ReactJS is a JavaScript UI library developed by Meta.

Although its creators claimed that React is not an MVC framework, it exudes the MVC ideology, albeit with some tweaks tailored explicitly to web UI development. React advocates a strict one-way data flow as its guiding principle to prevent complex UI callbacks and state management in vanilla JavaScript web development. In React, the UI state is managed by the state mechanism or third-party libraries such as Redux - separate from the JSX markups completely. The presentation layer, authored in JSX, solely represents each state in the UX design.

State describes the UI status; views are visual presentations of different states. Any user interactions can trigger a state update, resulting in React rendering a new view to reflect the new state. Users should not manipulate UI directly and keep track of the information inside views, e.g., hiding or showing an error message, enabling or disabling a button, or setting values of a form input value.

Component

ReactJS components are the fundamental building blocks in React. Under the hood, they are regular JavaScript functions that return UI elements. You can consider a React component a piece of code that encapsulates related HTML, CSS & and JavaScript to achieve a specific UX goal.

To avoid the pain of imperative programming in JavaScript, that is, to manipulate HTML DOM elements using rudimental JavaScript DOM API, React introduced a syntax called JSX, allowing HTML-like markups to be embedded inside JavaScript code, decoratively. You describe what should be on the screen, and React figures out the rest.

Babel and TypeScript can compile JSX into regular JavaScript functions that browsers understand - without needing React outside of the development environment.

The JSX transformer will turn code like this:

function App() {
  return <h1>Hello World</h1>;
}

into this:

// Inserted by a compiler (don't import it yourself!)
import { jsx as _jsx } from 'react/jsx-runtime';
 
function App() {
  return _jsx('h1', { children: 'Hello world' });
}

With the introduction of JSX, React Components bear strong similarities to HTML, including markup syntax, how they are composed, and the ability to nest components. To distinguish React components from regular HTML tags, a React class name must start with a capital letter and uses camel cases (it is a JavaScript class, after all.). E.g., <Header /> instead of <header />. React treats lowercase tags as regular HTML tags by default. In addition, adjacent JSX tags must be wrapped inside a single parent tag in each component. You can use <> and </>(shorthand version of the <Fragment> component) to avoid unnecessarily extra tags.

React components should be kept as pure functions, meaning strictly the same input, same output every single time. Components should mind their own business. They should not update anything outside the scope of the components or cause unintended side effects.

Rendering

React begins its life in the browser by calling createRoot (from react-dom/client) and, subsequently, the render() function of the root object returned from the createRoot call.

Any React app UI update follows a three-step process: trigger, render & commit. Component state change will result in update requests being queued (the triggering process). React renders the requesting components and all of their children, computes what DOM elements need to be modified in its virtual DOM, and commits the parts of the DOM, that is, to update the DOM inside the browser. If the rendering result is the same as the previous one, React won’t touch the DOM.

Strict Mode

When Strict Mode is on (<StrictMode></StrictMode>), React mounts every component twice in development env to ensure the components are pure functions and return the same output from the same input every single time. This stress test can also surface errors from not implementing the clean-up logic in useEffect.

Props

Props is an important mechanism for React Components.

In HTML, we assign different values to tag attributes to influence how the markup is rendered, e.g., setting the href of an <a /> to make it open different links. Similarly, we can encapsulate information into a single JavaScript object (the props object) and pass it to React components to control their behaviours. Since React Components are regular JavaScript functions under the hood, the props are passed as regular JavaScript function parameters. Properties of the props do not have to be strings like HTML attributes; they can be objects, arrays and functions. You often destructure the props object into named values individually:

function Header({ title, size = 48 }) {
  return <h1>{title ? title : 'Default'}</h1>;
}

Props in React follow a one-way data flow rule, which means data only travel down the component tree and never travels up, to make the React UI more composable by avoiding complicated controls and dependencies between parent and children UI elements.

When you nest content inside JSX, the component will receive the content through a prop named children. Component with a children prop is like having a “hole” that can be “filled in” by its parent components with any JSX - the component does not need to know what is being rendered inside it. This is extremely useful for wrapper components such as grids, tabs or cards.

import Avatar from './Avatar.js';
 
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}
 
export default function Profile() {
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi',
          image: 'YfeOqp2'
        }}
      />
    </Card>
  );
}
 

Props do not have to be static. Components may receive different props over time - rather than the static values when the components are created. Props reflect the status of a component at a moment in time. However, props are always immutable. Passing in a different set of props results in a fresh component rendering.

State

Unlike props, which are passed as function parameters from parents to children in a read-only fashion, state keeps track of component-specific information that is likely to change over time due to user interactions. In other words, the state is the component’s memory.

Components create state via the React useState API, and store a snapshot of the state values inside the component, which will be kept fixed during that render lifecycle. You can pass states as props to children components, but the updating logic should strictly sit inside the component where the state is created. When children components must notify parents of certain changes, handler functions that trigger parent state updates should be passed down to children components in the form of props or React context.

useState & useReducer

React provides a useState hook for components to manage state.

function SocialWidget() {
  // `useState` always returns two things.
  // The first is the variable, followed by the function that sets the value.
  const [likes, setLikes] = React.useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return (
    <div>
      {/* ... */}
      <button onClick={handleClick}>Likes ({likes})</button>
    </div>
  );
}

useState does two things. First, it retains values between renders. Second, it provides a setter function to update the value and triggers a fresh component render.

Preserving and resetting states

Given that React renders the component fresh (one-way data flow) every time its state changes (local variables will be recreated when the component is recreated), how does useState survive subsequent component renders? React accomplishes this by internally holding an array of state pairs for every component. It matches the different state pair by the order they are called for each component, avoiding keeping track of the variable names defined inside the component. (Read more on React Hooks: Not Magic, Just Arrays.). This design requires that the state declarations be predictable. Hence state pairs must be declared at the top of a component and cannot be nested inside conditional control flows such as if or switch, which may result in unpredictable behaviours.

React will keep the state for a component around as long as the same component is rendered at the same position in the React UI tree. That is, It’s the position of the component, not the JSX markup, that matters to React state management. The moment you stop rendering a component, its state is destroyed.

React uses the key attribute to determine the component position in the UI tree. Components with different keys will be treated as independent components even if placed in the same position with identical JSX markups. This is particularly useful when the state of a component needs to be reset between renders (give them different keys). In addition, because React keeps track of components based on their keys, components’ positions can be shifted between renders without losing their state.

🚨If a component’s definition is nested inside another component, it will get recreated as a new component (type) every time the containing component is rendered and lose its state - even though the JSX markup is identical. It is recommended to define components at the top level, not nest their definitions.

state Immutability

Setting state inside a component may feel like regular JavaScript variable manipulation. In reality, it is far from what it seems. useState is an API. Calling the API (via setters) triggers a component render - with its props, states and JSX computed based on the values from the state that React keeps somewhere else safe. The newly created component is a new instance whose states will be kept constant by React throughout that render lifecycle. E.g., if you define const [count, setCount] = useState(0); at the top of a component and you call setCount(count + 1) five times in a button click event handler later, the count for the next render will be 2, not 6. This is because the count state is fixed in its current render lifecycle and will only obtain a new value when rendered next time. Calling setCount(count + 1) merely tells React how to compute the count state for the next render; it will not update the value for the current render.

React batches the state updates requests, that is, waits for all the code inside event handlers have run. To set the same state value multiple times through the state update queue, you can use an updater function in the format of setCount(n => n + 1). If you call this five times in an event handler in the previous example, the value of the state will be 6 for the next render. This is because, under the hood, React executes the updater functions queued from the current component execution with the next render.

State should be treated as immutable (read-only). Do not mutate them locally; update them with setter methods provided by useState, which will trigger a new render of the components to reflect the desired state. This is especially important when non-primitive types, such as objects or arrays, are used for the state variables.

Instead of mutating a state object, create a copy, update the properties as desired, and trigger a render by passing the new copy to its setter.

When using an Array type as state, do not reassign array items or use methods that mutate the array, such as pop() or push(). Instead, create a copy through non-mutable methods such as filter() or map().

⚠️ The ... spread operator can be used to copy object properties or array items, but be aware that it only creates shallow copies of objects, therefore, will not work for ‘nested’ objects. E.g., ```

const [list, setList] = useState([{'seen': false, 'heard': false}, 'a', 'b']);
 
const nextList = [...list];  
nextList[0].seen = true; // Problem: mutates list[0]  
setList(nextList);

will not work because setting nextList[0].seen will change list[0], too, due to the fact that they both point to the same object.

Use a tool such as Immer instead.

Effects

All JavaScript variables defined inside a component are reactive variables. They are recreated from scratch for every re-render of the component.

In React, you write event handlers for user input, such as pressing a button, typing text into a form input or clicking a link. When a user takes an action, React will execute the corresponding event handler wired to that user input.

Rules of using Effects

  • Each Effect must represent a separate synchronisation process. Mixing unrelated responsibilities in a single useEffect results in desperate dependencies being combined into a single list, leading to unnecessary code execution or even infinite loops.
  • When using props as useEffect dependencies, the parent component may re-create a function or construct an object and pass them as the prop with every re-render, unnecessarily resulting in a fresh synchronisation. To prevent this, deconstruct the prop object or execute the function outside of useEffct and only list the absolute necessary primitive values as dependencies.
  • Whenever you write an Effect, consider whether you can wrap it into a custom Hook. Effect, as an escape hatch used for synchronising with an external system, should not appear in your React code often. Wrapping Effect inside a customer Hook separates code dealing with external APIs from React code, allowing you to communicate your intent clearly and making code easier to read.

Effect Event

Effect event is an escape hatch that makes states and props (including event handlers) non-reactive by wrapping them into an useEffectEvent call. This allows useEffect to call the ‘event handlers’ without specifying them as dependencies.

import { useState, useEffect } from "react";
// useEffectEvent is still an experimental feature as of this sample code
/* // package.json
* {
*   "dependencies": {
*     "react": "experimental",
*     "react-dom": "experimental"
*  },
*/
import { experimental_useEffectEvent as useEffectEvent } from "react";
 
export default function Timer(increment) {
  const [count, setCount] = useState(0);
 
  // This wraps the `setCount` inside an escape hatch to become non-reactive
  // It will not trigger the `useEffect` to re-sync as it's not listed
  // as a dependency (but the `useEffect` can still use it)
 
  const onTick = useEffectEvent(() => {
    console.log("Interval Tick");
    setCount(count => count + increment);
    // Getting up-to-date values without triggering useEffect re-sync
  });
 
  useEffect(() => {
    console.log("Creating an connection.");
    const id = setInterval(onTick, 1000);
    
    return () => {
      console.log("Disconnecting an connection.");
      clearInterval(id);
    };
  }, []);
 
  return <h1>Connection: {count}</h1>;
}

Hooks

Custom Hooks

Custom Hooks let you share stateful logic but not state itself. Each call to the same Hook is completely independent. Calling a Hook is equivalent to replacing the call with the actual code of the Hook. Multiple calls to the same Hook are the same as duplicating the Hook code logic multiple times. To share a state between components, lift it up and pass it down as a prop.

Custom Hooks should be kept pure, like your React Components.

Essential Packages

@types/react

The React type definitions@types/react is a community-maintained package that helps TypeScript engineers use React correctly.