Skip to Content
React in a Week
Day 4 · State & escape hatches

Thinking in React

A five-step method for turning a design into components and deciding where state should live.

On Day 3 you added state to a single component. Real apps have many components, and the hard question stops being "how do I use useState?" and becomes "which component should hold this state, and how do the others get it?"

The React team has a short, famous answer called Thinking in React. It is a five-step recipe you can follow every time you start a new screen. In this lesson you will walk through it with a small book filter.

Step 1: Break the UI into a component hierarchy

Imagine a designer hands you this mockup:

+---------------------------------------+
| [ Search...            ]              |   <- SearchBar
| [x] Only show unread                  |
|---------------------------------------|
| FICTION                               |   <- CategoryRow
|   Dune ............ Frank Herbert     |   <- BookRow
|   Piranesi ........ Susanna Clarke    |
| NON-FICTION                           |
|   Sapiens ......... Yuval Noah Harari |
+---------------------------------------+

Draw a box around every piece that has one clear job and give it a name. A component should ideally do one thing. If it grows to do several things, split it.

  • FilterableBookTable holds the whole thing.
  • SearchBar holds the text box and the checkbox.
  • BookTable shows the list.
  • CategoryRow shows one heading, such as "FICTION".
  • BookRow shows one book.

Nested boxes become nested components. That gives you a tree:

FilterableBookTable
├── SearchBar
└── BookTable
    ├── CategoryRow
    └── BookRow

Step 2: Build a static version

Build a version that renders the data but does nothing when you click or type. Use only props, no state. Static versions need a lot of typing and very little thinking, so it helps to do them first and separately.

Live code· Static book table

Data flows one way: from the top component down through props. Unread books are bold. Typing in the box does nothing yet, and that is fine.

Step 3: Find the minimal state

To make the UI interactive, users need to change some data. That changing data is state. The key rule is don't repeat yourself: keep the smallest possible set of state, and calculate everything else when you need it.

List every piece of data in the app, then ask three questions about each one:

  1. Does it stay the same over time? Then it is not state.
  2. Is it passed in from a parent through props? Then it is not state.
  3. Can you calculate it from existing state or props? Then it is definitely not state.

Whatever is left is probably state. For the book table:

DataState?Why
The original list of booksNoIt is passed in as a prop
The search textYesIt changes over time and can't be calculated
The checkbox valueYesIt changes over time and can't be calculated
The filtered list of booksNoYou can calculate it from the books, the search text and the checkbox

So you need exactly two pieces of state: filterText and unreadOnly.

Step 4: Decide where state lives

Now decide which component owns each piece of state. For each piece:

  1. Find every component that renders something based on that state.
  2. Find their closest shared parent.
  3. Put the state there (or higher, if that makes more sense).

SearchBar needs the filter values to show them in the inputs. BookTable needs them to filter the rows. Their closest shared parent is FilterableBookTable, so the state goes there and flows down as props.

Step 5: Add inverse data flow

Data now flows down. But when the user types, the input inside SearchBar must update state that lives in FilterableBookTable. A child can't change a parent's state directly. Instead, the parent passes down its setter function as a prop, and the child calls it. This is inverse data flow.

Live code· Interactive book table

Type "pi" or tick the checkbox. Notice that the filtered rows are calculated during render inside BookTable. They are never stored in state.

Quick check

In a todo app you store todos in state. Which of these should ALSO be state?

Try it

Add a category filter

Add a <select> to SearchBar with the options "All", "Fiction" and "Non-fiction". Only books in the chosen category should appear. Follow the steps: is it state? Where does it live? How does the child change it?

Live code· Challenge: category filter
Show solution

The category changes over time and can't be calculated, so it is state. Both SearchBar (to show the selected option) and BookTable (to filter) need it, so it lives in their shared parent, App.

import { useState } from 'react';
import { BOOKS } from './books.js';

function BookTable({ books, filterText, category }) {
  const visible = books.filter((book) => {
    const matchesText = book.title.toLowerCase().includes(filterText.toLowerCase());
    const matchesCategory = category === 'All' || book.category === category;
    return matchesText && matchesCategory;
  });
  return (
    <ul>
      {visible.map((book) => (
        <li key={book.title}>
          {book.title} <small>({book.category})</small>
        </li>
      ))}
    </ul>
  );
}

function SearchBar({ filterText, onFilterTextChange, category, onCategoryChange }) {
  return (
    <>
      <input
        value={filterText}
        placeholder="Search..."
        onChange={(e) => onFilterTextChange(e.target.value)}
      />
      <select value={category} onChange={(e) => onCategoryChange(e.target.value)}>
        <option>All</option>
        <option>Fiction</option>
        <option>Non-fiction</option>
      </select>
    </>
  );
}

export default function App() {
  const [filterText, setFilterText] = useState('');
  const [category, setCategory] = useState('All');
  return (
    <>
      <SearchBar
        filterText={filterText}
        onFilterTextChange={setFilterText}
        category={category}
        onCategoryChange={setCategory}
      />
      <BookTable books={BOOKS} filterText={filterText} category={category} />
    </>
  );
}
Quick check

Two sibling components both need to read and change the same value. Where should the state live?

Day 4 progress

0/10

On this page