# Thinking in React: build the whole thing

> The capstone. Take a mock-up, break it into components, build it static, find the smallest possible state, decide where it lives, and wire it up.

Source: https://karakabakov.com/courses/react-for-beginners/thinking-in-react/

---

[&larr; React for Beginners](https://karakabakov.com/courses/react-for-beginners/)

# Thinking in React: build the whole thing

Lesson 11 of 12 · 16 min · Updated August 14, 2026

Thinking in React is a five-step procedure for turning a mock-up into a working application: break it into components, build it static, find the minimal state, decide where that state lives, and wire it up. Every lesson so far has answered a question you were handed. This one starts where real work starts: with a picture and nothing else.

We are building a reading list. It shows books, lets you filter by text, lets you hide the ones you have finished, lets you mark a book as read, and shows a count. Sketch it:

*The mock-up, with step one already done to it.*

The procedure for turning that into React is always the same five steps, and worth following even when it feels laborious - it is what stops you writing state you do not need.

## Step 1: break the picture into components

Draw boxes around anything that is one thing. A component should do one job, the same way a function should.

- `App` - the whole page
- `Filters` - the text box and the checkbox
- `BookList` - the list
- `BookRow` - one book
- `Summary` - the count at the bottom

The hierarchy falls out of the drawing: `Filters`, `BookList` and `Summary` sit inside `App`, and `BookRow` sits inside `BookList`.

Do not agonise. If a box turns out to be wrong, moving JSX between components is the cheapest change you can make.

## Step 2: build it static, with no state at all

Write the whole thing with data flowing down as props and nothing interactive. No `useState`, no handlers.

```
const books = [
  { id: 1, title: "The Shipping Forecast", author: "Kate Bell", read: false },
  { id: 2, title: "Deep Work", author: "Cal Newport", read: true },
  { id: 3, title: "The Art of Doing Science", author: "Richard Hamming", read: false },
];

function BookRow({ book }) {
  return (
    <li>
      <input type="checkbox" checked={book.read} readOnly />
      <strong>{book.title}</strong> <span>{book.author}</span>
    </li>
  );
}

function BookList({ books }) {
  if (books.length === 0) return <p>Nothing matches.</p>;
  return (
    <ul>
      {books.map((book) => (
        <BookRow key={book.id} book={book} />
      ))}
    </ul>
  );
}
```

This half is deliberately dull, and doing it first is the whole trick. You are building the part that cannot go wrong while your attention is fresh, so that when you get to state you are thinking about one thing rather than two.

(`readOnly` is there because a `checked` with no `onChange` is an input React controls but nothing can change - it would warn otherwise. It goes away in step 5.)

## Step 3: find the minimal state

Now list everything the app deals with, and cross out anything you can work out from something else.

| Fact | State? | Why |
| --- | --- | --- |
| The list of books | state | Nothing else can produce it. |
| The filter text | state | It comes from the user and nothing else knows it. |
| Whether to hide read books | state | Same. |
| The filtered list | not state | It is books + filter + checkbox. |
| The unread count | not state | It is a .filter().length away. |
| Whether each book is read | not state on its own | It is a field on the book. |

Three pieces. Everything else is arithmetic.

This is lesson 9’s rule applied deliberately rather than by accident, and it is the step that decides whether the app stays easy to reason about. The test is the same one: *could I work this out from something else I already have?*

## Step 4: where should each piece of state live?

For each piece of state, find every component that reads it and put the state in their nearest common parent.

- Filter text: read by `Filters` (to display it) and needed for filtering, which happens where the list is decided. Common parent: `App`.
- Hide-read: same. `App`.
- The books: read by `BookList` and changed by `BookRow`. Common parent: `App`.

All three end up in `App`. That is normal for something this size, and not a sign you have done it wrong. In a larger app you would push each one down as far as it will go.

## Step 5: wire it up

Now add state, pass functions down, and derive everything else during render.

```
import { useState } from "react";

const initialBooks = [
  { id: 1, title: "The Shipping Forecast", author: "Kate Bell", read: false },
  { id: 2, title: "Deep Work", author: "Cal Newport", read: true },
  { id: 3, title: "The Art of Doing Science", author: "Richard Hamming", read: false },
];

function Filters({ query, onQueryChange, hideRead, onHideReadChange }) {
  return (
    <div>
      <input
        value={query}
        onChange={(e) => onQueryChange(e.target.value)}
        placeholder="Filter"
      />
      <label>
        <input
          type="checkbox"
          checked={hideRead}
          onChange={(e) => onHideReadChange(e.target.checked)}
        />
        Hide read
      </label>
    </div>
  );
}

function BookRow({ book, onToggleRead }) {
  return (
    <li>
      <input
        type="checkbox"
        checked={book.read}
        onChange={() => onToggleRead(book.id)}
      />
      <strong>{book.title}</strong> <span>{book.author}</span>
    </li>
  );
}

function BookList({ books, onToggleRead }) {
  if (books.length === 0) return <p>Nothing matches.</p>;
  return (
    <ul>
      {books.map((book) => (
        <BookRow key={book.id} book={book} onToggleRead={onToggleRead} />
      ))}
    </ul>
  );
}

function Summary({ unread, total }) {
  return (
    <p>
      {unread} of {total} unread
    </p>
  );
}

function App() {
  const [books, setBooks] = useState(initialBooks);
  const [query, setQuery] = useState("");
  const [hideRead, setHideRead] = useState(false);

  const visible = books.filter((book) => {
    const matches = (book.title + book.author)
      .toLowerCase()
      .includes(query.toLowerCase());
    return matches && (!hideRead || !book.read);
  });

  const unread = books.filter((book) => !book.read).length;

  function handleToggleRead(id) {
    setBooks(
      books.map((book) =>
        book.id === id ? { ...book, read: !book.read } : book,
      ),
    );
  }

  return (
    <main>
      <h1>Reading list</h1>
      <Filters
        query={query}
        onQueryChange={setQuery}
        hideRead={hideRead}
        onHideReadChange={setHideRead}
      />
      <BookList books={visible} onToggleRead={handleToggleRead} />
      <Summary unread={unread} total={books.length} />
    </main>
  );
}

export default App;
```

That is a working application, and every idea in this course is in it.

Read `App` once more, because it is the shape of nearly every React component you will write from here. Three pieces of state at the top, two derived values calculated during render, and one function that changes state by building a new array rather than modifying the old one. Then markup that hands each child exactly what it needs and nothing else.

Notice what is *not* there. There is no code that runs when something changes: no effect keeping `unread` in step with `books`, because `unread` is recalculated every render and cannot drift, and no stored copy of the filtered list. Leaving that machinery out is deliberate.

## Things worth trying

- Add a “clear filters” button. Notice you do not have to tell anything to update.
- Add a book with a form, using lesson 8. Where does the new `id` come from?
- Sort the list alphabetically. Remember `sort` mutates - use `toSorted()` or copy first.
- Change `key={book.id}` to `key={index}` - you will need `(book, index)` in `BookList`’s `map` - then filter while a checkbox is focused. Lesson 5, live.
- Try moving `query` into `Filters` and watch the filtering break. That is why it lives in `App`.

## Recap

The procedure is: draw the boxes, build it static with props only, list every fact and cross out the ones you can calculate, put each remaining fact in the nearest component that contains everyone who needs it, then add handlers that replace state rather than modify it. The order matters more than it looks - most React apps that become hard to work with went wrong at step 3, by storing something they could have derived.

Next, and last: what this course left out, and what is worth learning after it.

[&larr; Previous

## Talking to the outside world: useEffect and fetching](https://karakabakov.com/courses/react-for-beginners/effects-and-data/) [Next &rarr;

## Where to go from here](https://karakabakov.com/courses/react-for-beginners/what-next/)

[&larr; All lessons](https://karakabakov.com/courses/react-for-beginners/) [Next lesson &rarr;](https://karakabakov.com/courses/react-for-beginners/what-next/)
