Choosing the state structure
Five principles for shaping state well, and how React keeps or resets state based on position and key.
A well-shaped state is easy to update and hard to break. A badly shaped state forces you to keep several values in sync by hand, and sooner or later you forget one. A few minutes spent on the shape of your state saves hours of debugging.
Five principles
The React docs list five principles. You don't have to memorize them; each one is a way of removing duplication.
- Group related state. If two values always change together, such as
xandyof a mouse position, store them in one object. - Avoid contradictions. If two booleans can never both be true, use one value instead.
- Avoid redundant state. If you can calculate a value from props or other state, don't store it.
- Avoid duplication. Don't store the same object in two places.
- Avoid deep nesting. Deeply nested objects are painful to update immutably. Prefer flat data.
Avoid contradictions
Here is a form with two booleans. Nothing stops them from both being true, which would be a bug.
// Avoid: isSending and isSent could both be true
const [isSending, setIsSending] = useState(false);
const [isSent, setIsSent] = useState(false);Replace them with one status value that can only be one thing at a time:
You can still have readable names like isSending, but they are calculated from status, so they can never disagree.
Avoid duplication: store the ID, not the object
Say you show a list of books and highlight the selected one. A natural first try is to store the whole selected book:
// Avoid: the selected book is a second copy of an item in `books`
const [books, setBooks] = useState(initialBooks);
const [selectedBook, setSelectedBook] = useState(initialBooks[0]);Now if you rename a book in books, selectedBook still holds the old title. Store the ID and look the book up during render:
Edit the title of the chosen book. The "You picked" line updates instantly, because there is only one copy of each book.
Which of these is redundant state?
State lives at a position in the tree
Here is something surprising. State is not stored "inside" a component function. React stores it next to the position where that component is rendered in the tree. As long as the same component type stays at the same position, React keeps its state.
Try it: click "Add one" a few times, then tick the checkbox.
Sarah "inherits" Taylor's score. From React's point of view nothing changed: there is a Counter at the same spot both times, with a different prop. So it keeps the state.
Resetting state with a key
To tell React "this is a different counter, start fresh", give each one a different key. You used key for list items on Day 2. It works anywhere: when the key changes, React throws away the old component and its state and creates a new one.
{isPlayerA ? (
<Counter key="Taylor" person="Taylor" />
) : (
<Counter key="Sarah" person="Sarah" />
)}Now each player keeps a separate score from zero. You will use this trick again in the "You might not need an Effect" lesson to reset a whole form when the user picks a different item.
Reset the note when the book changes
This app shows a note editor for the selected book. Type a note for "Dune", then pick "Piranesi". The draft from Dune is still there. Fix it so each book starts with an empty draft, without adding any new state or Effects.
Show solution
Give NoteEditor a key that changes with the selected book:
<NoteEditor key={selected.id} book={selected} />When selectedId changes, the key changes, so React creates a new NoteEditor with a fresh draft.
Day 4 progress