Lesson 77 of 80
Most React bugs aren't exotic — they're the same handful of habitual mistakes, worn smooth by repetition. Recognizing the shape is most of the fix.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A small set of habits causes a disproportionate share of React bugs, and they tend to look reasonable while you're writing them. None of these require you to know something obscure — they require noticing a familiar shape and reaching for the fix instead.
// Anti-pattern: copying a prop into state
function UserPanel({ user }) {
const [name, setName] = useState(user.name); // only read once, on mount
return <input value={name} onChange={(e) => setName(e.target.value)} />;
}
// Fix: let the key decide when to reset, don't duplicate the source of truth
function UserPanel({ user }) {
return <input defaultValue={user.name} key={user.id} />;
}
useState(user.name) looks like it keeps name in sync with the prop, but useState's argument is only read on the component's first render. If the parent later passes a different user, name silently keeps the old value — the input looks broken, and nothing in the code obviously says why. The fix depends on what you actually want: if the input should reset whenever a genuinely different user is shown, keying the component by user.id tells React to unmount and remount it — a clean reset, not a stale copy. If local edits need to survive the parent re-rendering, that's what an uncontrolled input with defaultValue is for in the first place.
items.push(newItem); setItems(items) passes back the exact same array reference — React's bail-out check sees no change and skips the update. setItems([...items, newItem]) creates a new reference, which is what the check actually needs to see.key breaks the moment the list reorders, inserts, or deletes — React matches elements by key across renders, and an index doesn't travel with the item it was assigned to. A row's local state (an open dropdown, a focused input) can end up attached to the wrong row after a reorder, even though nothing else about the code looks wrong.The stale-name bug is the same problem this track's derived-state lesson names directly: don't duplicate a value you could just read. The index-as-key bug is the direct subject of this track's keys-and-lists lesson. And the remounting component is a consequence of how this track's reconciliation lesson says React actually decides what to keep versus throw away between renders — by type identity, not by what the code visually looks like.