Rendering & Reconciliation

Roughly 20% of a typical loop — and 20% of the mock exam here.

What actually triggers a re-render

Three things schedule a re-render: a state update inside the component, a re-render of its parent, or a change in a context value it reads. The classic trap answer is “when props change.” Props alone never schedule anything — a component re-renders because its parent rendered and produced new element objects. React.memo exists for exactly this: the child skips that forced render when props are shallow-equal.

Two follow-ups interviewers reach for:

  • Calling set with an Object.is-equal value bails out — React may still invoke the component once, but children skip.
  • Render and commit are separate phases: render computes the next tree; commit applies minimal DOM mutations. “It re-rendered” does not mean “it touched the DOM.”

Reconciliation, diffing, and why index keys break

React matches elements by position in the tree plus element type. Same type at the same position: the instance survives, state is preserved, props update in place — even if every prop changed. Different type at the same position: React unmounts the whole subtree, destroys its state, and mounts fresh. Defining a component inside another component breaks this — a new function identity each render reads as a different type, resetting state every render.

Keys override position matching in lists. With key={item.id}, state follows the item across reorders. With key={index}, state follows the slot: delete the first row and every remaining row inherits its former neighbor’s input text and checked state. Index keys are safe only for static, append-only lists. A changed key is a deliberate reset lever — <Chat key={contact.id} /> remounts when the contact switches.

Batching, StrictMode, and concurrent rendering

Since React 18, updates batch automatically everywhere — event handlers, promises, setTimeout, native listeners. Several set calls in one tick yield one re-render; flushSync is the opt-out. Trap: state read right after set is still the old value; updates queue for the next render.

StrictMode is development-only and changes nothing in production. It double-invokes component bodies (and initializer/updater functions) to expose impure renders, and runs an extra setup-plus-cleanup cycle for effects and ref callbacks to expose missing cleanup. “React 18 renders everything twice” is the wrong answer — that is StrictMode, in dev.

Concurrent rendering makes a render pass interruptible: React can pause low-priority work, handle urgent input, and discard a stale work-in-progress tree rather than block the main thread.

useTransition vs useDeferredValue

Both mark work as non-urgent. useTransition returns [isPending, startTransition] — use it when you own the set call: the urgent update (the keystroke) commits first while the expensive one renders in an interruptible background pass. React 19 accepts async functions inside transitions, but updates after an await need a fresh startTransition wrapper. useDeferredValue(value) fits values you do not set — a prop or custom-hook result: the UI keeps the stale value while the new one renders in the background. Unlike debouncing there is no fixed delay, and abandoned renders cost nothing. Rule of thumb: own the setter, transition; only have the value, defer. Neither may drive a controlled text input’s own state.

Suspense, portals, hydration

Suspense renders a fallback while children suspend (lazy components, Suspense-enabled data fetching, use). During a transition or deferred re-render, React keeps showing previous content instead of dropping to the fallback.

createPortal(children, domNode) renders into a different DOM node — modals, tooltips — while the component keeps its place in the React tree: context still flows in, and events bubble up the React tree, not the DOM tree — a favorite gotcha.

Hydration: hydrateRoot attaches React to server-rendered HTML instead of rebuilding it, so client output must match the server’s exactly. Mismatches come from typeof window branches, Date.now() or random values, and locale-dependent formatting. React warns in dev and may recover, but the worst case is handlers bound to the wrong elements. Fixes: render identically on both sides, move client-only branches into a useEffect two-pass, or suppressHydrationWarning on one unavoidable node — one level deep, and text is never patched.

Sample questions

6 of the 40 questions this domain carries in practice mode — expand one to check yourself before drilling.

1. Which of the following actually causes a React component to re-render?
  1. mutating a ref's current property from an event handler on the component
  2. assigning a new value to a module-level variable that the component's JSX reads during render
  3. a state update in the component, a re-render of its parent, or a change in a context it reads
  4. any DOM event that fires inside the component's subtree, even without a handler attached

Answer: C. Renders start from state updates, parent renders, or context changes; refs and plain variables are invisible to React, so mutating them schedules nothing.

2. A child component with no memo wrapper re-renders even though every prop it receives is identical between renders. The most likely cause is:
  1. its parent re-rendered, and unmemoized children render along with the parent
  2. React compares props on every render and re-renders when the comparison fails, so a hidden prop must have changed
  3. the browser fired a resize event, which always re-renders mounted components
  4. the child uses JSX spread syntax, which defeats React's prop tracking entirely

Answer: A. By default React re-renders a rendering component's entire subtree; prop equality only matters once you opt in with memo or the React Compiler.

3. Every consumer of a context re-renders whenever the provider's parent renders, even though the data inside the value never changes. The usual culprit is:
  1. the value prop is a fresh object literal each render, so consumers see a new identity
  2. context consumers always re-render together with the provider's parent component, by design
  3. the provider is missing a key, which makes React remount it on every render
  4. useContext caches its result by reference only in optimized production builds

Answer: A. Context change detection uses Object.is on the value prop; memoizing the object with useMemo keeps its identity stable across provider renders.

4. A teammate claims React re-renders a component whenever its props change. The more precise statement is:
  1. props are compared with deep equality on every update, and any difference found re-renders the whole subtree
  2. props changes re-render only class components, while function components rely on hooks
  3. props only change because the parent re-rendered, and it is the parent's render that re-renders the child
  4. props changes are coalesced by the scheduler, so re-renders happen at most once per frame

Answer: C. React never watches props; a child renders because its parent did, and memo merely lets it skip that render when props are shallow-equal.

5. A setter is called with a value Object.is considers equal to the current state, yet the component function still logs one more run — while its children do not. This is:
  1. a stale closure feeding the log output from a previous, cached render pass
  2. a StrictMode double-invocation applied to the state bailout code path
  3. expected — React may still render the bailing component once more, but skips the subtree and commits nothing
  4. a known bug worked around by keying the component on its own state value

Answer: C. The docs note React sometimes needs to run the component itself before confirming the bailout; it discards the output, so children and DOM stay untouched.

6. To tweak styling, a developer conditionally wraps ChatRoom in a plain div when a flag is on — and the moment the flag flips, ChatRoom's draft message disappears. Why?
  1. the element at that tree position changed from ChatRoom to div, so the old subtree unmounted, ChatRoom included
  2. div elements cannot host stateful children unless every child carries a key
  3. the flag update and the wrapper's render landed in two different batches and raced
  4. CSS containment on the new wrapper reset the input element's value attribute

Answer: A. Reconciliation compares tree positions level by level: a new wrapper changes the type at ChatRoom's old slot, and a type change always discards state.

Drill this domain in practice mode →

Independent community study resource — not affiliated with or endorsed by Meta Platforms, Inc. React is a trademark of Meta Platforms, Inc. All questions and study notes are original, written from the official React documentation. Everything runs in your browser; nothing you answer is stored or transmitted.