Local State Exercise
A guided exercise in using DevTools to find unnecessary re-renders and fixing them by pushing state down.
The best way to internalise the "push state down" rule is to feel the difference with your own hands -- first watching everything flash in DevTools, then watching only the relevant components flash after the fix.
The Setup
Take any component where all state lives at the top level. The signature of the problem:
- A parent component with multiple
useStatecalls - Those state values passed as props through components that don't use them
- Every state change triggers a full re-render of the tree
If you enable "Highlight updates when components render" and see the entire page flash when you type in a single input, you've found one.
The Exercise
Step 1 -- Measure. Record in the Profiler before touching anything. Note the total render time and which components appear in the flame graph for a single keystroke.
Step 2 -- Identify. Which state only affects a specific subtree? An input's draft value doesn't need to live in the root component if nothing outside the form reads it.
Step 3 -- Move it. Cut the useState from the parent. Paste it into the component that actually needs it. Delete the props that were threading it through.
Step 4 -- Measure again. Record the same interaction. Compare the flame graphs.
What Good Looks Like
After the refactor, the root component often ends up looking like this:
function App() {
return (
<div>
<Header />
<CreateThought /> {/* owns its own draft state */}
<ThoughtList /> {/* doesn't know CreateThought exists */}
</div>
);
}A root component with no props being passed down is a healthy sign. Each subtree owns what it needs. Changes in one don't cascade into others.
The Numbers in Context
After pushing state down, you might see render times like 1–2ms for an input change. The browser paints at 60fps -- that's a 16.6ms frame budget. A 1.3ms render means you have 15ms to spare.
At that point, stop optimising. It doesn't matter how many times something flashes in DevTools if the total time is imperceptible. You cannot see something that takes less than a single frame.
The goal isn't zero re-renders. It's correct re-renders that stay within budget.
The Pattern to Watch For
Pushing state down often reveals a second problem: callback functions defined inline are redefined on every render, giving child components new prop references each time -- defeating any React.memo you might add.
Pushing State Down covers this in detail with a code example. The fix is useCallback, which is the next topic.
Practice what you just read.
Keep reading