Day 4: Managing state and escape hatches
Decide where state lives, tame it with reducers, and step outside React with refs, Effects, data fetching and custom hooks.
Yesterday you made the Reading List interactive. You can add books, change their status and delete them. Today is about what happens when an app grows beyond one component: where should each piece of state live, how do you keep update logic readable, and how do you talk to things outside React, such as the browser, localStorage and a server?
This is the biggest day of the course, so it comes in two halves. The morning is about managing state: a method for structuring components, lifting state up, shaping it well and moving logic into a reducer. The afternoon is about escape hatches: refs, Effects, when not to use Effects, fetching data and writing your own hooks. Take breaks between the halves. Everything after today builds on these ideas.
Time plan
| Lesson | Time | What you'll do |
|---|---|---|
| Thinking in React | 25 min | Turn a mockup into components and find the minimal state |
| Sharing state | 25 min | Lift state up and build the FilterBar |
| Choosing the state structure | 30 min | Avoid redundant state, and reset state with key |
| Extracting logic into a reducer | 30 min | Write booksReducer with four actions |
| Referencing values with refs | 25 min | Remember values without re-rendering and focus inputs |
| Synchronizing with Effects | 35 min | Connect to outside systems and clean up after yourself |
| You might not need an Effect | 30 min | Replace unnecessary Effects with simpler code |
| Fetching data | 35 min | Load search suggestions without race conditions |
| Custom hooks | 30 min | Build useLocalStorage so your list survives a refresh |
| Project checkpoint | 35 min | Put it all together in the Reading List |
That's about 5 hours in total.
What you'll build today
By the end of the day your Reading List will:
- filter books by status with a
FilterBar(All, Want, Reading, Done), with the filtered list derived during render rather than stored; - manage every change through a
booksReducerwith the actionsadded,statusChanged,deletedandrated; - let you rate finished books from 1 to 5;
- remember your books after a page refresh, thanks to a
useLocalStoragecustom hook; - suggest books as you type, through a
SearchSuggestionscomponent that fetches from a fake API and cleans up stale requests.
Today's lessons
Thinking in React
A five-step method for going from mockup to components and state.
Sharing state between components
Lift state up so components stay in sync.
Choosing the state structure
Five principles for clean state, plus resetting with key.
Extracting logic into a reducer
Gather every update into one pure function with useReducer.
Referencing values with refs
Remember values without re-rendering and reach DOM nodes.
Synchronizing with Effects
Keep components in sync with systems outside React.
You might not need an Effect
Derived values, event handlers and keys instead of Effects.
Fetching data with Effects
Loading and error states, and the ignore flag for race conditions.
Reusing logic with custom hooks
Write your own hooks, starting with useLocalStorage.
Time to bring today's pieces together in your reading-list project. You built each part in a lesson; now you will wire them into one App. The code below builds directly on the Day 3 checkpoint. If your own names differ a little, keep yours and follow the ideas.
1. Lift the filter and derive the list
Create src/FilterBar.js from the Sharing state lesson. In App, add const [filter, setFilter] = useState('all') and calculate the visible books during render:
const visibleBooks = filter === 'all' ? books : books.filter((b) => b.status === filter);Don't store visibleBooks in state. It is derived from books and filter.
2. Move updates into booksReducer
Create src/booksReducer.js with the four actions from the reducer lesson. Keep your Day 3 handlers (handleAddBook, handleStatusChange, handleDelete), but replace their bodies with dispatch calls. handleStatusChange works out the next status with the same nextStatus helper and dispatches statusChanged. Remember to create the ID with crypto.randomUUID() in the handler, not in the reducer.
3. Add ratings
In BookCard, show five star buttons when a book's status is 'done'. Each one calls onRate(book.id, n), which dispatches { type: 'rated', id, rating: n }.
4. Persist with useLocalStorage
Create src/useLocalStorage.js from the custom hooks lesson. Store the books with it and keep the reducer by writing a small dispatch that applies booksReducer through the setter:
const [books, setBooks] = useLocalStorage('reading-list:books', initialBooks);
function dispatch(action) {
setBooks((current) => booksReducer(current, action));
}5. Add search suggestions
Create src/api.js with the fake searchBooks function and src/SearchSuggestions.js from the fetching lesson. When the user picks a suggestion, App dispatches an added action with a new ID and the status 'want'. If you want to try the real network in your local project, swap api.js for the Open Library version shown in that lesson.
The full Day 4 code
Here is the complete app. Try every feature: filter, add, change status, rate, delete, search, then refresh the preview and check that your books are still there.
If your books get into a strange state while experimenting, click Reset to sample books in the preview. In your local project, you can also open DevTools, go to Application, then Local Storage, and delete the reading-list:books entry.
Tomorrow you will convert this app to TypeScript, move the books into a context provider, add pages with React Router, and replace the hand-written fetching with TanStack Query.
Day 4 progress