It occurs when a child component tries to access a store item that no longer exists. Can you explain how to prevent it
Classic 'parent removes an item, child rendering that item crashes during the same render' bug. Fixes (best to worst): guard with conditional rendering in the parent (don't render the child if the item is gone); read the item by id in the child and bail to null if undefined; use a key that changes on removal so the child unmounts cleanly; move the read to a selector that returns undefined safely.
This is one of React's most common subtle bugs — and it shows up in any store-driven UI (Redux, Zustand, context). The shape: parent removes an item from the store, the child rendering that item is still mounted for one render, and it tries to read store.items[id] which is now undefined → Cannot read properties of undefined (reading 'name').
Why it happens
function List() {
const items = useStore((s) => s.items);
return items.map((id) => <Item key={id} id={id} />);
}
function Item({ id }) {
const item = useStore((s) => s.itemsById[id]);
return <div>{item.name}</div>; // CRASH if item was deleted
}When the user deletes item 5:
- The store updates —
itemsById[5]is now undefined. - The list selector returns a new array without
5. - Before the parent re-renders, the deleted child can re-render once (because the store notified all subscribers). It reads its slice → undefined → crashes.
The race is between "all subscribers re-render" and "parent re-renders and unmounts the child."
Fix 1 — guard in the child (defensive)
function Item({ id }) {
const item = useStore((s) => s.itemsById[id]);
if (!item) return null; // bail safely
return <div>{item.name}</div>;
}Simple and robust. The child renders null for one tick, the parent's next render unmounts it.
This is the most reliable fix in most stores — the child shouldn't assume its data exists.
Fix 2 — pass the data, not the id
function List() {
const items = useStore((s) => s.items); // array of full objects
return items.map((item) => <Item key={item.id} item={item} />);
}
function Item({ item }) {
return <div>{item.name}</div>;
}The child never reads from the store; the parent owns the lookup. When the parent re-renders (with the item removed), the child unmounts cleanly. No race.
Fix 3 — selector returns undefined safely + nullish guards
const item = useStore((s) => s.itemsById[id]);
return <div>{item?.name ?? "—"}</div>;Same as Fix 1 but inline. Fine for small components.
Fix 4 — equality / shallow-stability so child doesn't re-render at all
If the store offers selector-based subscriptions (Zustand, RTK with shallow equality), make the child's selector return only what changed — and not re-render at all when other items change. Zustand:
const item = useStore((s) => s.itemsById[id], shallow);But this doesn't help against the actual deletion — the child still gets notified. So combine with Fix 1.
Fix 5 — key changes on remove
If the key includes a version or the parent re-keys the list, the child unmounts when its key disappears. Less direct; only helps in specific store designs.
Why Redux historically had this problem
Redux's connect/useSelector notifies all subscribers on every dispatch. If a list and its children both subscribe, the children can render with stale ids referencing now-missing state. Redux Toolkit's createEntityAdapter selectors return undefined safely, and the pattern is to guard.
The general principle
A child reading from a store by id must tolerate "id no longer exists" because it can render one extra tick after deletion.
This is true of any store with broadcast notifications, regardless of library.
Interview framing
"It's the classic 'render race after deletion' — the store notifies all subscribers, so the child can run one render after its data is gone before the parent unmounts it. The bug is the child crashing on store.itemsById[id] being undefined. The cleanest fix is for the child to guard: if (!item) return null — defensive but reliable. The architecturally better fix is for the parent to pass the full item down rather than just the id, so the child never reads from the store and there's no race. If using a selector-based store, return undefined safely and pair with shallow equality to avoid the noise re-renders. The general rule: a child that reads by id from a store must tolerate that id disappearing for one render."
Follow-up questions
- •Why does the child render after its data is deleted?
- •Why doesn't React batch the parent and child re-renders to avoid this?
- •When would you pass the id vs the whole object?
- •How does this manifest in Redux vs Zustand?
Common mistakes
- •Reading store.itemsById[id] without an undefined guard.
- •Assuming React unmounts children synchronously when state changes.
- •Adding try/catch as a defensive band-aid instead of fixing the read.
Performance considerations
- •Guarding the child with `if (!item) return null` is essentially free. Memoization to avoid the noise re-renders is the perf concern, not correctness.
Edge cases
- •Item updated (not deleted) but shape changed — same class of bug.
- •Nested stores where a parent's data depends on a child's existence.
- •Optimistic deletes that get rolled back.
Real-world examples
- •Redux Toolkit's entity adapter selectors return undefined; pair with guards.
- •Any chat / list / collaborative UI where items disappear.