Skip to main content
CodeOath
← All posts

React120 min total · 17 parts

React Fundamentals: Components, Hooks, and the Virtual DOM

Part 17 of 17 · ~3 min

Common Mistakes Worth Remembering

Every one of these is a bug this dashboard had at some point in this reference. The chapter reference is where it was taken apart.

  • Reading state to compute the next state, in a loop or across several updates. Our bulk-reject counter went up by one instead of three. Use setCount(c => c + 1) whenever the new value depends on the old one. (Chapter 3)
  • A closure gone stale inside an effect, because some value it depends on never made it into the dependency array. Our autosave overwrote four hundred words of notes with an empty string every ten seconds for exactly this reason. Reach for a ref or the functional updater if you genuinely don't want the effect re-running — muting the lint warning only postpones the bug. (Chapter 7)
  • Mutating state or props in place. candidate.stage = "rejected" or items.push(x) changes the data without changing the reference, React's comparison sees nothing new, and no re-render happens — until an unrelated render makes the change appear, at an unpredictable moment. Always produce a new array or object. (Chapters 2 and 6)
  • Missing keys, or the array index as a key on a list that can be sorted, filtered or edited. Our half-typed note followed position instead of person and landed on the wrong candidate's record. This leaks state, not only visuals. (Chapter 4)
  • A hook below a conditional return. The early return added during a refactor is the version that actually happens, and it shifts every hook after it into the wrong slot. (Chapter 12)
  • {value && <Thing />} where value can be 0. Our zero-scoring candidate got a bare 0 in their score cell. Compare explicitly. (Chapter 1)
  • Copying a prop into useState and expecting it to follow. The review form kept showing the previous candidate's notes. Use the prop, or reset the component with a key. (Chapter 3)
  • React.memo on a child that receives a fresh function or object literal every render. The comparison can never succeed, so you paid for a comparison and skipped nothing. (Chapter 9)
  • One large context holding unrelated values. Typing in the filter box re-rendered eight hundred rows that only wanted the reviewer's name. Split by how often each value changes, and memoize the provider value. (Chapter 11)
  • No cleanup on a subscription. Ten stacked keydown listeners from ten opened drawers, all still firing. If the effect starts it, the cleanup stops it — and Strict Mode's double-mount in development will tell you about it immediately. (Chapter 7)
  • Memoizing everywhere by default, on renders nobody measured. Every dependency array is a correctness liability you now maintain forever. Measure, then optimise. (Chapters 9 and 15)

The same bugs, in a different application

One caution about a reference built around a single running example: it is possible to learn "the dashboard's key bug" rather than "the key bug". So here is the identical mechanism somewhere with no table, no candidates and no drawer — a chat panel:

{messages.map((message, index) => (
  <Message key={index} message={message} />          // same bug, new clothes
))}

A new message arrives at the top of the list, every index shifts by one, and React matches by position. The "show original translation" toggle a reader had open stays on row 3 and is now attached to somebody else's message. No table anywhere, same reconciliation rule, same consequence.

Or the stale closure, in an analytics widget with no forms in sight:

useEffect(() => {
  const id = setInterval(() => sendHeartbeat(activeTab), 30000);
  return () => clearInterval(id);
}, []);                             // reports "overview" forever, whatever tab is open

If those two read as obvious, the mechanisms transferred, which was the point of building one thing rather than twenty.


Most production React is written in TypeScript, which turns a good share of the prop mistakes above into compile errors — see TypeScript Fundamentals for that side of the same ecosystem. And the patterns here reward being typed out rather than read: state, effects and the closures they capture are all runnable as exercises in the code lab.