Skip to content

Thinking in React

Fresh

React changes the way you think about UI design and building. Here is the mental model for building any UI with React.

The 5-Step Process

flowchart TD
    A[1. Break UI into component hierarchy] --> B[2. Build a static version]
    B --> C[3. Find the minimal state]
    C --> D[4. Identify where state lives]
    D --> E[5. Add inverse data flow]

Step 1: Break the UI into a Component Hierarchy

Draw boxes around every component and subcomponent in the mockup and name them. Use the single responsibility principle: a component should ideally only do one thing.

Step 2: Build a Static Version in React

Build a version that renders the UI from your data model without adding any interactivity. Don't use state at all for the static version; state is reserved for interactivity.

jsx
function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">{category}</th>
    </tr>
  );
}

function ProductRow({ product }) {
  const name = product.stocked ? product.name :
    <span style={{ color: 'red' }}>{product.name}</span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category}
        />
      );
    }
    rows.push(
      <ProductRow product={product} key={product.name} />
    );
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

Step 3: Find the Minimal but Complete Representation of UI State

Think of state as the minimal set of changing data that your app needs to remember.

The key principle: Don't Repeat Yourself (DRY). Figure out the absolute minimum representation of the state your application needs and compute everything else on demand.

Ask three questions about each piece of data:

  1. Does it remain unchanged over time? If so, it isn't state.
  2. Is it passed in from a parent via props? If so, it isn't state.
  3. Can you compute it based on existing state or props? If so, it definitely isn't state.

Step 4: Identify Where Your State Should Live

For each piece of state:

  1. Identify every component that renders something based on that state
  2. Find their closest common parent component
  3. The common parent (or a component above it) should own the state

Step 5: Add Inverse Data Flow

Pass callback functions from parent to child so that children can update the parent's state:

jsx
function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

  return (
    <div>
      <SearchBar
        filterText={filterText}
        inStockOnly={inStockOnly}
        onFilterTextChange={setFilterText}
        onInStockOnlyChange={setInStockOnly}
      />
      <ProductTable
        products={products}
        filterText={filterText}
        inStockOnly={inStockOnly}
      />
    </div>
  );
}

Key Takeaway

Data flows down through props. Events flow up through callbacks. State lives in the closest common ancestor of the components that need it.