Command Palette

Search for a command to run...

Hectal
PHASE 16Advanced ~25 min· topic 2 of 10

Topic 16.2

Reconciliation, Fiber and the Diffing Rules

In one line

Reconciliation is how React compares the new element tree with the previous one: elements of the same type in the same position (with the same key) are updated in place and keep their state, anything else is destroyed and rebuilt; Fiber is the internal data structure that lets React do this work in small, interruptible pieces.

Think of it like this

Spot the difference. You hold two nearly identical pictures side by side and compare them corner by corner: same cat in the top-left? Keep it. A dog where the cat was? That's a change. You don't compare every object with every other object in the whole picture, you compare what's in the same place. React compares the old and new screen descriptions the same way, place by place.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Reconciliation
React comparing the new element tree with the old one to decide what to keep, update, add or remove.
Diffing
Another name for that comparison: finding the differences between two trees.
Heuristic
A practical rule of thumb that is fast and almost always right, instead of a perfect but slow method.
Element type
What an element is: a tag name like "div", or a component function like Counter.
Position
Where an element sits among its parent's children. Same type in the same position keeps its state.
Key
A label you give siblings so React can match them by identity instead of by position.
Fiber
React's internal object for one component or element: its type, props, state and links to parent, child and sibling.
Work-in-progress tree
The draft fiber tree React builds while rendering, before it becomes what's on screen.

Step by step

01React compares level by level

React starts at the root and compares the old and new elements in the same place. It never searches the whole tree for a moved subtree: if you move a component to a different parent, it is destroyed in the old place and created fresh in the new one.

React compares level by leveldiagram
Rendering diagram…

02Same type: update in place

For DOM elements React compares props and changes only the attributes and text that differ. For components it keeps the instance, calls it with the new props, and repeats the comparison on what it returns.

03Different type: tear down and rebuild

Swapping <div> for <section> around a counter destroys the counter too, even though the counter itself didn't change. Effects clean up, state is lost, DOM nodes are removed and created again.

04Never declare components inside components

Each render of Parent below creates a brand-new Input function. To React that's a new type every time, so the input is rebuilt on every keystroke and loses focus.

Bad.jsxwhole filejsx
function Parent() {
  const [text, setText] = useState("");
  // BUG: a new component type on every render
  function Input() {
    return <input value={text} onChange={(e) => setText(e.target.value)} />;
  }
  return <Input />;
}

05Keys match list items by identity

In a list, React matches children by key. Stable ids let React keep each row's state and DOM when items are added, removed or reordered. Index keys tie state to slots instead of items.

06Fiber makes the work interruptible

React walks the fiber tree in a loop: begin work on a fiber, go to its child, then its sibling, then back up to the parent. Between any two fibers it can check "is there something more urgent?" and yield to the browser.

Fiber makes the work interruptiblediagram
Rendering diagram…

Try it yourself

  1. 1

    Wrap A differently in each branch

    In the first example, change the A line to {fancy ? <div><Counter label="A" /></div> : <Counter label="A" />}. Predict: does A keep its count when flipped? Check the log. (No: the child at that position changes type between div and Counter.)

  2. 2

    Reset with a key

    Give B a key that changes: <Counter key={boxed ? "x" : "y"} label="B" /> inside the same div for both branches. Predict what the swap button does now. Changing a key is the clean way to say "start over".

  3. 3

    Reorder instead of insert

    In the key example, add a button that reverses the list (setPeople([...people].reverse())). Type notes in both lists, reverse, and compare which rows keep their notes.

Code & diagrams

Same type keeps state, a new type rebuilds New tab

Each counter is clicked twice. Flipping A's ternary keeps A's state: it's still a Counter at the same position. Swapping B's wrapper from div to section changes the type one level up, so B unmounts, mounts again and starts from 0.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

A: mounted
B: mounted
A: 2 | B: 2
--- flip A's branch
A: 2 | B: 2
--- swap B's wrapper
B: unmounted
B: mounted
A: 2 | B: 0
Index keys move state to the wrong row New tab

The script types "likes tea" next to Asha in both lists, then adds Meera at the top. With index keys the note stays in slot 0 and now sits next to Meera. With id keys it moves with Asha.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

key={index} Meera=likes tea, Asha=-, Ravi=-
key={id} Meera=-, Asha=likes tea, Ravi=-
How React decides, for each childdiagram
Rendering diagram…
A fiber, simplifiedjavascript
// Roughly what React keeps for each component or element (not the real field list).
const fiber = {
  tag: "FunctionComponent",   // what kind of fiber
  type: Counter,              // the function, or "div" for DOM elements
  key: "a",
  pendingProps: { label: "A" },
  memoizedProps: { label: "A" }, // props from the last finished render
  memoizedState: hook1,          // linked list of hooks: hook1.next -> hook2 ...
  stateNode: null,               // the DOM node for host elements
  return: parentFiber,           // parent
  child: firstChildFiber,
  sibling: nextSiblingFiber,
  alternate: otherTreeFiber,     // its twin in the current / work-in-progress tree
  lanes: 0,                      // pending update priorities
};

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Declare a component inside another

Move Row inside List (define function Row in List's body) and type in a note.

terminal
$ Preview
── what you'll see ──
After every keystroke the input loses focus and the typed letter disappears.

Myth vs fact

Myth

Keys are only there to silence a console warning.

Fact

Keys decide which state and DOM belong to which item. Wrong keys move state to the wrong row, as the live example shows.

Myth

React diffs the whole tree to find moved components.

Fact

React compares level by level and by position (or key among siblings). A component moved to another parent is rebuilt.

Myth

The virtual DOM is why React is fast.

Fact

Diffing is extra work compared with a perfect hand-written update. React's value is that it's fast enough while letting you write declarative code; the cheap part is skipping DOM work that isn't needed.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    List reconciliation in React is a single forward pass plus a map: React walks old and new children together while keys match, then puts the remaining old children in a Map keyed by key (or index) and looks each new child up. Moves are detected by comparing each matched child's old index with the last placed index, so moving one item to the end is cheap but moving the last item to the front marks every other item as moved.

  • ▸

    Fiber was designed for priorities. Every update gets a lane (a bit in a bitmask): discrete events like clicks are high priority, transitions are lower, hidden <Activity> content is lowest. The scheduler renders high-priority lanes first and can throw away an unfinished low-priority render.

  • ▸

    Because rendering builds a draft tree that may be discarded, render must be pure: no DOM changes, no subscriptions, no writes to outside variables. Side effects belong in event handlers and effects, which run only for committed work.

  • ▸

    key is not a prop: the component can't read props.key, and React warns if you try. Pass the id again under another name if the component needs it.

Remember this

  1. 1

    Reconciliation is the step after rendering where React compares the new React elements with the ones from the last render to decide which DOM nodes and which component instances to keep, update, create or delete. Comparing two arbitrary trees perfectly is a famously expensive problem (the best general algorithms take about O(n³) steps for n nodes: for 1,000 elements that's a billion steps). React instead uses two heuristics (rules of thumb) that make it O(n): it walks the tree once.

  2. 2

    Rule 1: different type means a different thing. If an element's type changes at a position (<div> becomes <section>, or <Counter> becomes <Timer>), React destroys the old subtree, including every component's state, DOM nodes and effects (cleanups run), and builds the new one from scratch. It doesn't try to reuse anything inside. This is why defining a component inside another component is a bug: each render creates a new function, a new type, so its state resets every time.

  3. 3

    Rule 2: same type, same position means the same thing. If the type stays the same, React keeps the component instance (and its state) or the DOM node, and only updates what changed: new props are passed in, changed DOM attributes are set. Position means the place in the parent's list of children. {show ? <A /> : <A />} is still one A at one position, so its state survives the switch. State belongs to a position in the tree, not to the JSX line that created it (Topic 6.4).

  4. 4

    Keys refine "position" in lists. Among siblings, React matches old and new children by key instead of by index. With stable keys (ids from your data), inserting at the top just moves the existing items down: every item keeps its state and DOM. With the index as key, inserting at the top shifts every index, so item 0's state goes to the new item and every row's DOM updates: slow, and wrong when rows hold state (an input, a checkbox, an animation). Keys also work outside lists: changing key on any element tells React "this is a new thing", the standard way to reset a component's state (Topic 3.3, Topic 6.4).

  5. 5

    Fiber is React's internal representation of the tree. Each component instance or DOM element on screen has a fiber: a plain object holding its type, key, props, state (the list of hooks), pending updates, and links to its parent (return), first child (child) and next sibling (sibling). Because the tree is a linked list, React can walk it one fiber at a time in a loop instead of with deep recursion, stop between fibers to let the browser handle input, and continue later. That ability is what makes concurrent rendering (Topic 16.7) and <Activity> (Topic 16.10) possible.

  6. 6

    Double buffering. React keeps two fiber trees: current (what is on screen now) and work-in-progress (the next version being rendered). Rendering builds the work-in-progress tree; nothing on screen changes yet, so it can be paused or thrown away. The commit applies the DOM changes in one go and then swaps the two: work-in-progress becomes current. Fiber was introduced in React 16 (2017) as a rewrite of the older recursive "stack" reconciler.

Explain it without notes

01

What are the two heuristics React's reconciliation relies on, and why are they needed?

02

Why does a component defined inside another component lose its state on every render?

03

What goes wrong with index keys when you insert at the top of a list?

04

What is a fiber, and what did the Fiber architecture make possible?

Practice

01

A form shows either a <LoginForm> or a <SignupForm> in the same place. Both render an email <input>. When the user switches, the typed email should be cleared. Should you do anything?

02

A profile page <Profile userId={id} /> keeps a draft comment in state. When the user navigates to another profile, the draft must reset. Show the simplest fix.

Trade-offs

  • ↔

    The O(n) heuristics are fast but rebuild a subtree whenever a wrapper's type changes, even if the inside didn't. Keep wrappers stable across branches when state should survive.

  • ↔

    Keys from data are correct but require stable ids; index keys are fine only for static lists that never reorder and whose rows hold no state.

Done when you can

  • I can explain the two diffing heuristics and why React uses them.

  • I can predict when a component keeps or loses its state.

  • I can choose correct keys and use a key to reset state.

  • I know why components must be declared at the top level.

  • I can describe what a fiber is and why it makes rendering interruptible.