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 likeCounter. - 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.
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.
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.
Try it yourself
- 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 betweendivandCounter.) - 2
Reset with a key
Give B a key that changes:
<Counter key={boxed ? "x" : "y"} label="B" />inside the samedivfor both branches. Predict what the swap button does now. Changing a key is the clean way to say "start over". - 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
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
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: 0The 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
Console
Nothing logged yet.Expected console output
key={index} Meera=likes tea, Asha=-, Ravi=-
key={id} Meera=-, Asha=likes tea, Ravi=-// 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.
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
Mapkeyed 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.
- ▸
keyis not a prop: the component can't readprops.key, and React warns if you try. Pass the id again under another name if the component needs it.
Remember this
- 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
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
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 oneAat 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
Keys refine "position" in lists. Among siblings, React matches old and new children by
keyinstead 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: changingkeyon 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
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
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
What are the two heuristics React's reconciliation relies on, and why are they needed?
Why does a component defined inside another component lose its state on every render?
What goes wrong with index keys when you insert at the top of a list?
What is a fiber, and what did the Fiber architecture make possible?
Practice
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?
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.