Fiber: React's unit of work
Read React's fibers live: the tag, type and stateNode fields, the child, sibling and return links, and the current and alternate trees, at v19.3.1.
- 01Read a fiber's fieldsSay what tag, type, key, stateNode, memoizedProps and memoizedState hold for a component and for a DOM element.
- 02Walk the fiber treeMove through the tree with child, sibling and return, and explain why React links fibers this way instead of using arrays.
- 03Explain current and work-in-progressDescribe how each fiber and its alternate take turns, how the FiberRoot points at the tree on screen, and what flags and lanes record.
A fiber for every piece of the page
Earlier lessons said that React "keeps track" of your components: which state belongs to which one, and what it showed last time. This lesson opens that up. For every component and every element on the page, React keeps a plain JavaScript object called a fiber. You can reach one from the page. Click the button.
import { useState } from "react"; export default function App() { const [report, setReport] = useState("Click the button."); function inspect(event) { const button = event.currentTarget; const key = Object.keys(button).find((name) => name.startsWith("__reactFiber$")); const fiber = button[key]; setReport(`tag ${fiber.tag}, type "${fiber.type}", its parent is a <${fiber.return.type}>`); } return ( <section> <h2>Your cart</h2> <button onClick={inspect}>Find my fiber</button> <p>{report}</p> </section> );}Line 7 takes the button element you clicked. Line 8 looks through the element's own keys for one that starts with __reactFiber$: react-dom adds that key to every DOM element it creates. Line 9 reads it, and line 10 shows three of its fields. The result reads tag 5, type "button", its parent is a <section>.
Every example in this lesson reads fields React keeps for itself. They aren't part of React's public API: they have no documentation, and they change between versions without warning. Read them to learn, or while debugging. Never build an app on them.
The key's name comes from ReactDOMComponentTree.js, lines 43 and 44:
const randomKey = Math.random().toString(36).slice(2);
const internalInstanceKey = '__reactFiber$' + randomKey;Line 1 makes a few random letters once, when react-dom loads. Line 2 adds them to __reactFiber$. The name is different on every page load, which is why line 8 of the example searches for the start of the name instead of writing it out. When react-dom creates a DOM element, it stores the element's fiber under that key (precacheFiberNode, line 110), so that a click on the element can find its way back to React.
tag 5 says it stands for a DOM element, type says which one, stateNode points at the real <button> on the page, and return points at the fiber of its parent, the <section>.A furniture workshop keeps a card for every piece of a job. The card says what the piece is, where it fits, what it looked like last time, and what still needs doing. Workers take one card at a time, do that piece's work, and move on to the next card. To change a piece, they fill in a fresh copy of its card, and swap the copy in only when all the work is ready.
- In real life: One card for each piece of a job: the table, each leg, each drawer
- In React: One fiber for each component and element
- In real life: What the piece is, written at the top of the card
- In React: tag and type
- In real life: The finished piece standing on the shop floor
- In React: stateNode: the real DOM element
- In real life: Pins that join a card to the one above, the first one below, and the next one beside
- In React: return, child and sibling
- In real life: A fresh copy of a card that you edit, then swap in for the old one
- In React: The alternate, used for the next render
A fiber is React's record of one component or element: what it is, where it sits in the tree, what it rendered last time, and what work is left. React keeps one for each piece of the tree and reuses it from render to render. It does its work one fiber at a time, so a fiber is also a unit of work.
What a fiber is: tag, type, key and stateNode
Every fiber is made by one constructor, FiberNode, in ReactFiber.js. The source uses Flow, a type checker like TypeScript, so read past the text after each colon. Here is how it starts:
function FiberNode(
this: $FlowFixMe,
tag: WorkTag,
pendingProps: mixed,
key: ReactKey,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;In plain words, line by line:
- Lines 1 to 7 take four values: the
tag, the props for this render, thekey, and amode(bits such as "this tree is in StrictMode").this: $FlowFixMeis only a note for Flow, not a value. - Line 8 starts the group of fields that say what this piece is.
- Line 9 stores the tag: a number for the kind of fiber, such as a function component or a DOM element.
- Line 10 stores the key you wrote in JSX, like
key="tea", ornull. - Lines 11 and 12 start
elementTypeandtypeempty. The function that makes a fiber from an element fills them in: the component function, likeCartItem, or the tag name, like"li". The two are the same for everything in this lesson. They differ for a few wrapped components, such as one made withlazy. - Line 13 starts stateNode empty. It will hold the object the fiber stands for outside React: for a DOM element, the real DOM node.
The tag numbers are in ReactWorkTags.js, which calls them work tags: the tag decides which work React does for the fiber. Here are the first eight:
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;There are 30 in all, one for each kind of thing React can render, such as SuspenseComponent (13) and MemoComponent (14). How to read the table: each row is a tag you meet in every app, what you write to get it, and what its stateNode holds.
| Tag | Name in ReactWorkTags.js | What you write to get it | What stateNode holds |
|---|---|---|---|
0 | FunctionComponent | A function component, like CartItem. | null. A function component has no object of its own. |
3 | HostRoot | Nothing: createRoot makes it. | The FiberRoot, React's object for the whole root. |
5 | HostComponent | A DOM element, like <li> or <button>. | The real DOM element on the page. |
6 | HostText | Text next to other children, like Items: {count}. | The real text node on the page. |
7 | Fragment | A <Fragment> or <> that isn't the whole return value. | null. |
8 | Mode | <StrictMode>, which every live example's main.jsx adds. | null. |
Now look at real ones. The example below is a fiber inspector. Click Tea $3 in the cart, then press Go to its return again and again.
import { useState } from "react";import { describeNode, fiberOf, nameOf, tagNames } from "./fiber.js"; function CartItem({ name, price }) { return <li>{name + " $" + price}</li>;} export default function App() { const [fiber, setFiber] = useState(null); return ( <> <section onClick={(event) => setFiber(fiberOf(event.target))}> <h2>Your cart</h2> <ul> <CartItem key="tea" name="Tea" price={3} /> <CartItem key="cake" name="Cake" price={4} /> </ul> <button>Checkout</button> </section> {fiber ? ( <dl> <dt>tag</dt> <dd>{fiber.tag} ({tagNames[fiber.tag] ?? "another kind"})</dd> <dt>type</dt> <dd>{nameOf(fiber)}</dd> <dt>key</dt> <dd>{String(fiber.key)}</dd> <dt>stateNode</dt> <dd>{describeNode(fiber.stateNode)}</dd> </dl> ) : ( <p>Click anything in the cart.</p> )} {fiber?.return && <button onClick={() => setFiber(fiber.return)}>Go to its return: {nameOf(fiber.return)}</button>} </> );}Line 13 reads the fiber of whatever you clicked, with fiberOf from the fiber.js tab (lines 15 to 18 there, the same search as before). Lines 21 to 31 show four fields, and line 35 moves to the fiber's return, its parent. Starting from Tea, the climb goes:
li: tag 5, HostComponent. ItsstateNodeis the<li>on the page.CartItem: tag 0, FunctionComponent, keytea, andstateNodenull. A function component has no DOM node of its own; theliit returns is its child.ul, thensection, thenApp: two elements and your component.Mode, tag 8: the<StrictMode>that every live example'smain.jsxwraps aroundApp.HostRoot, tag 3: the top of the tree. ItsstateNodeis the FiberRoot, and itsreturnisnull, so the button disappears. The root section below explains it.
How to read the second table: it groups all of a fiber's main fields by the question they answer. The rest of the lesson takes the groups one at a time.
| The question | The fields | What they hold |
|---|---|---|
| What is it? | tag, type, key, elementType | The kind of fiber, the component or tag name, and the key you gave it. |
| What does it stand for on the page? | stateNode | The DOM node, the FiberRoot, or a class instance. null for function components. |
| Where is it in the tree? | return, child, sibling, index | Links to its parent, its first child, and its next sibling, and its position. |
| What does it remember? | pendingProps, memoizedProps, memoizedState, updateQueue | The props for this render and the last one, the state (hooks for a function), and waiting updates. |
| What must happen next? | flags, subtreeFlags, deletions | Changes the commit must make here, and a summary of the ones below. |
| Does it have work waiting? | lanes, childLanes | Updates waiting on this fiber, and on any fiber below it, as bits. |
| Where is its other copy? | alternate | The same fiber in the other tree: current or work-in-progress. |
child, sibling and return: a tree made of links
You might expect each fiber to hold an array of its children. It doesn't. Here is how the Fiber type describes the links, in ReactInternalTypes.js:
// The Fiber to return to after finishing processing this one.
// This is effectively the parent, but there can be multiple parents (two)
// so this is only the parent of the thing we're currently processing.
// It is conceptually the same as the return address of a stack frame.
return: Fiber | null,
// Singly Linked List Tree Structure.
child: Fiber | null,
sibling: Fiber | null,
index: number,- Lines 1 to 4 explain
return: the fiber to go back to after this one is done, which is its parent. The comment compares it to the return address of a stack frame, the place a function goes back to when it returns (the call stack lesson shows those). - Line 5 is
return: the parent fiber, ornullat the top. - Line 7 names the shape: a singly linked list, where each item points only at the next one.
- Line 8 is
child: the first child only. - Line 9 is
sibling: the next child of the same parent, ornullafter the last one. - Line 10 is
index: the fiber's place among its siblings, 0 for the first.
sibling, and each one's return points back at the parent. Every fiber has a return; the picture draws two. index is each child's place among its siblings.Why links instead of arrays? With three links, React can get from any fiber to the next one with a single variable, and it can stop between any two fibers and pick up again later from that variable alone. The next lesson, the work loop, is built on exactly that. Walk the links yourself:
function CartItem({ name, price }) {
return <li>{name + " $" + price}</li>;
}
function Cart() {
return (
<section>
<h2>Your cart</h2>
<ul>
<CartItem key="tea" name="Tea" price={3} />
<CartItem key="cake" name="Cake" price={4} />
</ul>
<p>Total: $7</p>
</section>
);
}loadingYour cart
- Tea $3
- Cake $4
Total: $7
Press Follow child to go from Cart to its section, then keep going. Each button reads one field of the fiber you are on.
Press Follow child twice: from Cart to its section, then to the h2. Press it a third time: the h2 has no child fiber, because its only content is text, which React sets on the element instead of making a fiber for it. Press Follow sibling to reach the ul, and Follow return to climb back to the section. The list under the cart is every fiber below Cart, found with nothing but these three fields, in the order React starts work on them.
What a fiber remembers: props, state and updates
A fiber outlives the render that made it, so it's where React keeps what a component needs next time. Four fields hold it, in ReactInternalTypes.js:
// Input is the data coming into process this fiber. Arguments. Props.
pendingProps: any, // This type will be more specific once we overload the tag.
memoizedProps: any, // The props used to create the output.
// A queue of state updates and callbacks.
updateQueue: mixed,
// The state used to create the output
memoizedState: any,- Lines 1 and 2:
pendingPropsare the props for the render in progress, the "input" of this piece of work. - Line 3:
memoizedPropsare the props of the last finished render. "Memoized" means remembered. Comparing the two is how React notices that a parent passed something new. - Lines 5 and 6:
updateQueueholds waiting work. For the root, it holds the waitingroot.rendercalls. A function component keeps its waiting state updates in each hook instead, and usesupdateQueuefor its effects. - Lines 8 and 9:
memoizedStateis the state the last render used. For a function component, that's its hooks.
Press Add one twice, then Read my fiber.
import { useState } from "react";import { fiberOf, onScreen } from "./fiber.js"; function CartItem({ name, price }) { const [quantity, setQuantity] = useState(1); const [note] = useState("gift wrap"); const [report, setReport] = useState([]); function read(event) { let fiber = fiberOf(event.currentTarget); while (fiber.type !== CartItem) fiber = fiber.return; fiber = onScreen(fiber); const hook = fiber.memoizedState; setReport([ `memoizedProps: ${JSON.stringify(fiber.memoizedProps)}`, `first hook: ${hook.memoizedState}`, `second hook: ${hook.next.memoizedState}`, ]); } return ( <li> {name} ${price}, quantity {quantity}, {note} <button onClick={() => setQuantity(quantity + 1)}>Add one</button> <button onClick={read}>Read my fiber</button> {report.map((line) => <p key={line}>{line}</p>)} </li> );} export default function App() { return ( <ul> <CartItem name="Tea" price={3} /> </ul> );}Lines 10 and 11 start at the button's fiber and climb return until they reach the fiber whose type is CartItem. Line 12 makes sure it's the copy on screen; the double-buffering section below explains why there are two. Line 13 reads memoizedState. The result shows:
memoizedProps: {"name":"Tea","price":3}: the props from line 34.first hook: 3: the quantity from line 5, after your two clicks.second hook: gift wrap: line 17 followsnextfrom the first hook to the second, from line 6.
So a function component's memoizedState is its first hook, and each hook points at the next one: a linked list in the order you call them. That's why hooks must run in the same order every render (the Rules of Hooks lesson), and the how hooks work inside lesson reads that list in detail. It also explains preserving and resetting state: state belongs to a place in the tree because it lives on the fiber in that place. When React removes the fiber, the state goes with it.
The root: FiberRoot and the HostRoot fiber
The climb in the inspector ended at a fiber with tag 3 whose stateNode was "the FiberRoot". These are two different objects, and createRoot makes both. The FiberRoot isn't a fiber. It holds what React needs for the whole root, in ReactInternalTypes.js:
type BaseFiberRootProperties = {
// The type of root (legacy, batched, concurrent, etc.)
tag: RootTag,
// Any additional information from the host associated with this root.
containerInfo: Container,
// Used only by persistent updates.
pendingChildren: any,
// The currently active root fiber. This is the mutable root of the tree.
current: Fiber,- Line 3: its own
tag, a root tag, not a work tag.createRootalways makes a concurrent root. - Line 6:
containerInfo, the DOM element you passed tocreateRoot: your<div id="root">. - Line 8: a field only some renderers use.
- Lines 9 and 10:
current, "the currently active root fiber": the top fiber of the tree on screen.
That top fiber is the HostRoot fiber, tag 3. createFiberRoot joins the two, in ReactFiberRoot.js:
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(tag, isStrictMode);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;Line 3 makes the HostRoot fiber. Line 4 points the FiberRoot's current at it, and line 5 points the fiber's stateNode back at the FiberRoot. The comment on lines 1 and 2 calls this a cyclic construction: each one points at the other. Then createRoot stores that HostRoot fiber on your container under a second key, __reactContainer$ (ReactDOMRoot.js, line 252). Click Look at the root, then click it again.
import { useState } from "react";import { nameOf } from "./fiber.js"; export default function App() { const [lines, setLines] = useState([]); function look() { const container = document.getElementById("root"); const key = Object.keys(container).find((name) => name.startsWith("__reactContainer$")); const hostRoot = container[key]; const fiberRoot = hostRoot.stateNode; setLines([ `hostRoot.tag: ${hostRoot.tag}`, `fiberRoot.containerInfo is the #root div: ${fiberRoot.containerInfo === container}`, `below the root: ${nameOf(fiberRoot.current.child)}, then ${nameOf(fiberRoot.current.child.child)}`, `this HostRoot fiber is the current one: ${fiberRoot.current === hostRoot}`, ]); } return ( <> <button onClick={look}>Look at the root</button> <ul> {lines.map((line) => <li key={line}>{line}</li>)} </ul> </> );}Lines 8 to 10 read the HostRoot fiber from the container, and line 11 follows its stateNode to the FiberRoot. The first click lists:
hostRoot.tag: 3(line 13).fiberRoot.containerInfo is the #root div: true(line 14).below the root: Mode, then App(line 15): the root's child is the StrictMode fiber, and its child isApp.this HostRoot fiber is the current one: false(line 16).
The last line is the surprise. The container still holds the HostRoot fiber that createRoot made, yet the FiberRoot's current points at a different one. Click again and it says true; again, and it says false. Each click sets state, so React renders and commits, and every commit moves current. There are two HostRoot fibers, and they take turns.
Two trees: current and work-in-progress
React never changes the fibers on screen while it renders. It renders into a second copy of the tree, the work-in-progress tree, and the copy on screen is the current tree. Each fiber in one tree points at its twin in the other through alternate. Before React works on a fiber, it gets the twin from createWorkInProgress, in ReactFiber.js:
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// ... (development-only fields left out)
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// Needed because Blocks store data on type.
workInProgress.type = current.type;
// We already have an alternate.
// Reset the effect tag.
workInProgress.flags = NoFlags;
// The effects are no longer valid.
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
// ... (two branches for experimental and profiling builds left out)
}
// Reset all effects except static ones.
// Static effects are not specific to a render.
workInProgress.flags = current.flags & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;- Lines 1 and 2 take the
currentfiber and look at itsalternate. - Lines 3 to 17 run when there is no twin yet, the first time this fiber is updated. Lines 9 to 17 make one with the same tag, key, mode, type and
stateNode: one DOM element, two fibers. The comment on lines 4 to 8 names the technique, double buffering: React only ever needs two versions of the tree, and it makes the second one only when a fiber is updated. - Lines 19 and 20 link the two, both ways.
- Lines 21 to 32 run when the twin already exists: React reuses it, gives it the new props, and clears what was left from its last render.
- Lines 36 to 45 copy from the current fiber: its lanes, its first child, its props, its state and its update queue. The render then works on this copy, and the current fiber stays as it was.
alternate. The FiberRoot's current points at the tree on screen. A render fills in the other one, and the commit moves current across, so the two trees swap roles.You can watch one element's two fibers take turns. The example reads the fiber of the <strong> after every commit. Click Add tea three times and read the three lines each time.
import { useLayoutEffect, useRef, useState } from "react";import { fiberOf } from "./fiber.js"; export default function App() { const [count, setCount] = useState(0); const countRef = useRef(null); const reportRef = useRef(null); useLayoutEffect(() => { const fiber = fiberOf(countRef.current); const other = fiber.alternate; reportRef.current.textContent = [ `The <strong>'s fiber remembers ${fiber.memoizedProps.children}.`, `Its alternate remembers ${other ? other.memoizedProps.children : "nothing: there is none yet"}.`, `Each is the other's alternate: ${other ? other.alternate === fiber : "not yet"}.`, ].join("\n"); }); return ( <div> <p>Items in cart: <strong ref={countRef}>{count}</strong></p> <button onClick={() => setCount(count + 1)}>Add tea</button> <pre ref={reportRef} /> </div> );}The layout effect on lines 9 to 17 runs after every commit. Line 10 reads the fiber the DOM element points at, and line 11 its alternate. Here is what the lines say:
- Before any click: the fiber remembers 0, and there is no alternate yet. Nothing has been updated.
- After click 1: the fiber still remembers 0, and its alternate remembers 1. React made a twin for the render and committed it. The DOM element points at the fiber that created it, which is now the old copy.
- After click 2: the fiber remembers 2, and its alternate 1. React reused the old copy for this render, as lines 21 to 32 of
createWorkInProgressdo. - After click 3: 2 and 3. They keep taking turns, and line 15 shows that each one is the other's alternate.
That's also why the fiber.js tab has onScreen (its lines 39 to 43): to read state you need the copy whose tree the FiberRoot's current points at. React's own version, findCurrentFiberUsingSlowPath, handles more cases. The switch itself is one line in the commit, in ReactFiberWorkLoop.js:
// The work-in-progress tree is now the current tree. This must come after
// the mutation phase, so that the previous tree is still current during
// componentWillUnmount, but before the layout phase, so that the finished
// work is current during componentDidMount/Update.
root.current = finishedWork;Line 5 is the swap. The comment says when it happens: after React has changed the DOM (the "mutation phase"), and before it runs layout effects, so a layout effect already sees the new tree as current. The commit phase lesson goes through those phases.
Why two trees? While React fills in the work-in-progress tree, the current tree still matches the screen exactly. If the render stops halfway, as a transition's render can, or React throws it away, nothing on screen has changed and nothing is lost. When the render is done, the commit changes the DOM and moves one pointer.
Flags and lanes: notes for the next steps
The last fields FiberNode sets in every build are notes about work: what the commit must do, and which updates are waiting. (Profiling and development builds add a few timing and debugging fields after them.) From ReactFiber.js:
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;- Line 2:
flags, the changes the commit must make for this fiber. - Line 3:
subtreeFlags, every flag of every fiber below it, collected together. - Line 4:
deletions, the child fibers to remove. - Lines 6 and 7:
lanes, the updates waiting on this fiber, andchildLanes, the ones waiting anywhere below it. - Line 9:
alternate, its twin, from the section above.
Flags and lanes are numbers used as rows of switches: each bit means one thing. These are the first flags, in ReactFiberFlags.js:
// Don't change these values. They're used by React Dev Tools.
export const NoFlags = /* */ 0b0000000000000000000000000000000;
export const PerformedWork = /* */ 0b0000000000000000000000000000001;
export const Placement = /* */ 0b0000000000000000000000000000010;
export const DidCapture = /* */ 0b0000000000000000000000010000000;
export const Hydrating = /* */ 0b0000000000000000001000000000000;
// You can change the rest (and add more).
export const Update = /* */ 0b0000000000000000000000000000100;
export const Cloned = /* */ 0b0000000000000000000000000001000;
export const ChildDeletion = /* */ 0b0000000000000000000000000010000;Each value is written in binary (0b) with a single 1 in it. Line 4, Placement, means "insert this node into the DOM". Line 9, Update, means "change this node". Line 12, ChildDeletion, means "remove some children". React turns bits on and off with JavaScript's bitwise operators (the other operators lesson covers them). Here are the same operators on the same values:
const Placement = 0b10;
const Update = 0b100;
const ChildDeletion = 0b10000;
let flags = 0;
flags |= Placement;
flags |= Update;
console.log(flags);
console.log((flags & Update) !== 0);
console.log((flags & ChildDeletion) !== 0);
flags &= ~Placement;
console.log(flags);Lines 6 and 7 turn on two bits with |=, so line 9 prints 6: 2 plus 4. Line 10 asks whether the Update bit is on with & and prints true; line 11 asks about ChildDeletion and prints false. Line 12 turns Placement off with &= ~, so line 13 prints 4. One number holds many yes-or-no answers, and React can check one or combine many in a single step.
setQuantity in CartItem. Its fiber gets the lane in lanes, and every fiber above it gets it in childLanes, so the next render knows which way to go down. Right: the render finds that the <li>'s text must change. Its fiber gets Update in flags, and the fibers above collect it in subtreeFlags, so the commit knows which way to go down.The picture shows why the subtree and child versions exist. When the render finishes a fiber, it adds the fiber's flags to its parent's subtreeFlags. The commit can then skip any part of the tree whose subtreeFlags is 0. childLanes does the same job for the render: an update marks its own fiber's lanes and every parent's childLanes, so React can skip every part of the tree with no work waiting. The work loop lesson shows the render collecting both and skipping with childLanes; the commit phase lesson shows the commit skipping with subtreeFlags.
Lanes are bits too. Each one is a priority, from ReactFiberLane.js:
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;
export const SyncLaneIndex: number = 1;
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000;Line 5 is SyncLane, 2, for updates from clicks and other discrete events. Line 12 is DefaultLane, 32, for updates from other places, such as a timer. Transitions get lanes of their own, starting at TransitionLane1, 256. Watch a set function mark a lane: click Add tea, then Add tea in a transition twice.
import { startTransition, useState } from "react";import { fiberOf, onScreen } from "./fiber.js"; function lanesOf(event) { let fiber = fiberOf(event.currentTarget); while (fiber.type !== App) fiber = fiber.return; return onScreen(fiber).lanes.toString(2);} export default function App() { const [count, setCount] = useState(0); const [seen, setSeen] = useState("Click a button."); function addNow(event) { setCount(count + 1); setSeen(`After setCount in a click, App's lanes: ${lanesOf(event)}`); } function addInTransition(event) { startTransition(() => setCount(count + 1)); setSeen(`After setCount in a transition, App's lanes: ${lanesOf(event)}`); } return ( <> <p>Items in cart: {count}</p> <button onClick={addNow}>Add tea</button> <button onClick={addInTransition}>Add tea in a transition</button> <p>{seen}</p> </> );}Lines 4 to 8 climb from the button to App's fiber and return its lanes in binary. Line 15 calls setCount, and line 16 reads the lanes right after, while the click handler is still running and before React renders: 10, the SyncLane bit. Line 20 calls it inside startTransition, and line 21 reads 100000000, TransitionLane1. Click it again and the 1 moves one place left: React hands each new transition the next transition lane. The render then clears the lanes it handled. The lanes lesson explains how React picks and groups them.
Which component drew this button?
A real use for all this: you see a button on a big page and want to know which component rendered it. The return path answers it. Click anything in the shop.
import { useState } from "react";import { fiberOf } from "./fiber.js"; function componentPath(node) { const names = []; for (let fiber = fiberOf(node); fiber !== null; fiber = fiber.return) { if (typeof fiber.type === "function") names.unshift(fiber.type.name); } return names.join(" > ");} function Price({ value }) { return <strong>${value}</strong>;} function AddButton({ name }) { return <button>Add {name}</button>;} function ProductCard({ name, price }) { return ( <article> <h3>{name}</h3> <Price value={price} /> <AddButton name={name} /> </article> );} export default function App() { const [path, setPath] = useState("Click anything in the shop."); return ( <div onClick={(event) => setPath(componentPath(event.target))}> <ProductCard name="Tea" price={3} /> <ProductCard name="Cake" price={4} /> <p>{path}</p> </div> );}Lines 4 to 10 start at the clicked element's fiber and climb return to the top. Line 7 keeps only the fibers whose type is a function, the components, and puts each name in front. Click Add Tea and the result reads App > ProductCard > AddButton. Click the $4 and it reads App > ProductCard > Price. The <article> and <h3> fibers are on the path too; line 7 skips them.
React DevTools shows the same path when you pick an element in its Components tab, and it keeps working when React changes. A production build also renames your functions to save space, so type.name there can be a single letter. Keep code like this for learning and for a quick look while debugging.
When you read React's source, you'll see these fields everywhere. Sort each question by the field that answers it.
- Is this fiber a DOM element or a component?
- Which function does React call for it?
- Which item in the list is it, by the name you gave it?
- Which fiber is the first thing inside it?
- Which fiber does React go back to after this one?
- Where does it sit among its siblings?
- Which props did it last render with?
- What does its useState hold?
- Does its DOM element need a change in the commit?
- Is an update waiting somewhere below it?
- Where is the other copy of it?
Put each question under the group of fiber fields that answers it.
Common mix-ups
- "A fiber is the same thing as a React element." No. An element is the frozen description your component returns, made again on every render (the React elements lesson). A fiber is React's own object for that place in the tree, kept and changed across renders.
- "React makes new fibers on every render." No. It reuses them. Each fiber has at most one twin, its
alternate, andcreateWorkInProgressreuses the twin for the next render. - "A fiber keeps its children in an array." No. It points at its first child; the others follow through
sibling, and each points back withreturn. - "A DOM element always points at the fiber on screen." No. It points at the fiber that created it. After an update, that can be the old copy, as the two-trees example shows.
- "The FiberRoot is the top fiber." No. The top fiber is the HostRoot fiber, tag 3. The FiberRoot is a separate object that isn't a fiber, and its
currentpoints at the HostRoot fiber on screen. - "Fiber means React renders on another thread." No. React renders on the main thread. Fibers let it do the work in small pieces, one fiber at a time, and stop between them.
- "Reading __reactFiber$ is a handy trick for apps." No. The fields are private and change between versions. Use them to learn and debug, and use React DevTools in real work.
How to read the table: each row asks one question of a React element and of a fiber.
| The question | React element | Fiber |
|---|---|---|
| Who makes it? | Your component, by returning JSX. jsx() makes it. | React, in the render, from the element. |
| How long does it live? | One render. The next render makes new ones. | As long as the component stays in the same place. React reuses it. |
| Can it change? | No. Treat it as frozen. | Yes. React writes to it during every render and commit. |
| What does it hold? | type, key and props. | Those, plus state, links to other fibers, flags, lanes and its alternate. |
Practice exercises
Predict what the result shows after you click the badge. Then press Edit & run and click it to check.
import { useState } from "react";
function Badge() {
return <span>New</span>;
}
export default function App() {
const [answer, setAnswer] = useState("Click the badge.");
function check(event) {
const span = event.target;
const key = Object.keys(span).find((name) => name.startsWith("__reactFiber$"));
const fiber = span[key];
setAnswer(`${fiber.tag} and ${fiber.return.tag}`);
}
return (
<p onClick={check}>
<Badge /> {answer}
</p>
);
}import { useState } from "react";
function Badge() {
return <span>New</span>;
}
export default function App() {
const [answer, setAnswer] = useState("Click the badge.");
function check(event) {
const span = event.target;
const key = Object.keys(span).find((name) => name.startsWith("__reactFiber$"));
const fiber = span[key];
setAnswer(`${fiber.tag} and ${fiber.return.tag}`);
}
return (
<p onClick={check}>
<Badge /> {answer}
</p>
);
}The span's fiber is a HostComponent, tag 5. Its return is Badge's fiber, a FunctionComponent, tag 0. So the result reads 5 and 0.
Predict what the result shows after you click Mint.
import { useState } from "react";
export default function App() {
const [answer, setAnswer] = useState("Click Mint.");
function check(event) {
const li = event.target;
const key = Object.keys(li).find((name) => name.startsWith("__reactFiber$"));
setAnswer(`index ${li[key].index}, key ${li[key].key}`);
}
return (
<>
<ul onClick={check}>
{["Tea", "Cake", "Mint"].map((name) => <li key={name.toLowerCase()}>{name}</li>)}
</ul>
<p>{answer}</p>
</>
);
}import { useState } from "react";
export default function App() {
const [answer, setAnswer] = useState("Click Mint.");
function check(event) {
const li = event.target;
const key = Object.keys(li).find((name) => name.startsWith("__reactFiber$"));
setAnswer(`index ${li[key].index}, key ${li[key].key}`);
}
return (
<>
<ul onClick={check}>
{["Tea", "Cake", "Mint"].map((name) => <li key={name.toLowerCase()}>{name}</li>)}
</ul>
<p>{answer}</p>
</>
);
}Mint is the third child of the ul, so its index is 2, and its key is the lowercased name, mint. The result reads index 2, key mint.
Finish childKeys so it returns the key of every child fiber of the ul, using only child and sibling. Then click the list.
import { useState } from "react"; function fiberOf(node) { const key = Object.keys(node).find((name) => name.startsWith("__reactFiber$")); return node[key];} // TODO: return the key of every child fiber of `fiber`, in order.function childKeys(fiber) { return [];} export default function App() { const [keys, setKeys] = useState([]); return ( <> <ul onClick={(event) => setKeys(childKeys(fiberOf(event.currentTarget)))}> <li key="tea">Tea</li> <li key="cake">Cake</li> <li key="mint">Mint</li> </ul> <p>Child keys: {keys.join(", ")}</p> </> );}import { useState } from "react"; function fiberOf(node) { const key = Object.keys(node).find((name) => name.startsWith("__reactFiber$")); return node[key];} function childKeys(fiber) { const keys = []; for (let child = fiber.child; child !== null; child = child.sibling) { keys.push(child.key); } return keys;} export default function App() { const [keys, setKeys] = useState([]); return ( <> <ul onClick={(event) => setKeys(childKeys(fiberOf(event.currentTarget)))}> <li key="tea">Tea</li> <li key="cake">Cake</li> <li key="mint">Mint</li> </ul> <p>Child keys: {keys.join(", ")}</p> </> );}The loop starts at fiber.child, the first li, and follows sibling until it reaches null. That visits the three children in order: tea, cake, mint.
Each click records whether the button's fiber has no alternate yet, then renders. Predict what the button shows after two clicks.
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const [seen, setSeen] = useState([]);
function check(event) {
const button = event.currentTarget;
const key = Object.keys(button).find((name) => name.startsWith("__reactFiber$"));
setSeen([...seen, button[key].alternate === null]);
setCount(count + 1);
}
return (
<button onClick={check}>
Clicked {count} times. No alternate yet: {seen.join(", ")}
</button>
);
}import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const [seen, setSeen] = useState([]);
function check(event) {
const button = event.currentTarget;
const key = Object.keys(button).find((name) => name.startsWith("__reactFiber$"));
setSeen([...seen, button[key].alternate === null]);
setCount(count + 1);
}
return (
<button onClick={check}>
Clicked {count} times. No alternate yet: {seen.join(", ")}
</button>
);
}On the first click, nothing has been updated, so the button's fiber has no alternate: true. That click renders, and createWorkInProgress makes the twin. On the second click it exists: false.
Finish countFibers so it counts every fiber from the HostRoot down, with a loop and the three links only, no recursion. Then click the button.
import { useState } from "react"; // TODO: count every fiber from root down, without recursion:// child to go down, sibling to go right, return to go back up.function countFibers(root) { return 0;} export default function App() { const [count, setCount] = useState(null); function countAll() { const container = document.getElementById("root"); const key = Object.keys(container).find((name) => name.startsWith("__reactContainer$")); const hostRoot = container[key].stateNode.current; setCount(countFibers(hostRoot)); } return ( <section> <h2>Your cart</h2> <button onClick={countAll}>Count the fibers</button> <p>{count === null ? "Not counted yet" : `Fibers: ${count}`}</p> </section> );}import { useState } from "react"; function countFibers(root) { let count = 0; let fiber = root; while (fiber !== null) { count += 1; if (fiber.child !== null) { fiber = fiber.child; continue; } while (fiber !== root && fiber.sibling === null) fiber = fiber.return; if (fiber === root) break; fiber = fiber.sibling; } return count;} export default function App() { const [count, setCount] = useState(null); function countAll() { const container = document.getElementById("root"); const key = Object.keys(container).find((name) => name.startsWith("__reactContainer$")); const hostRoot = container[key].stateNode.current; setCount(countFibers(hostRoot)); } return ( <section> <h2>Your cart</h2> <button onClick={countAll}>Count the fibers</button> <p>{count === null ? "Not counted yet" : `Fibers: ${count}`}</p> </section> );}The loop counts a fiber, goes down to its child if it has one, and otherwise climbs return until it finds a sibling. The tree has 7 fibers: HostRoot, Mode, App, section, h2, button and p. The texts are set on their elements, so they have no fibers.
Finish itemKeyFor so it returns the key of the CartItem around whatever you clicked. Then click Remove Cake.
import { useState } from "react"; function fiberOf(node) { const key = Object.keys(node).find((name) => name.startsWith("__reactFiber$")); return node[key];} function CartItem({ name }) { return ( <li> <button>Remove {name}</button> </li> );} // TODO: climb from the clicked node to the nearest CartItem fiber, and return its key.function itemKeyFor(node) { return null;} export default function App() { const [key, setKey] = useState(null); return ( <> <ul onClick={(event) => setKey(itemKeyFor(event.target))}> <CartItem key="tea" name="Tea" /> <CartItem key="cake" name="Cake" /> </ul> <p>You clicked the item with key: {String(key)}</p> </> );}import { useState } from "react"; function fiberOf(node) { const key = Object.keys(node).find((name) => name.startsWith("__reactFiber$")); return node[key];} function CartItem({ name }) { return ( <li> <button>Remove {name}</button> </li> );} function itemKeyFor(node) { for (let fiber = fiberOf(node); fiber !== null; fiber = fiber.return) { if (fiber.type === CartItem) return fiber.key; } return null;} export default function App() { const [key, setKey] = useState(null); return ( <> <ul onClick={(event) => setKey(itemKeyFor(event.target))}> <CartItem key="tea" name="Tea" /> <CartItem key="cake" name="Cake" /> </ul> <p>You clicked the item with key: {String(key)}</p> </> );}The loop climbs from the button's fiber through the li to the first fiber whose type is CartItem, and returns its key. A click on Remove Cake shows cake.
Check your understanding
For each question, picture the fiber tree: which fiber, which field, and which of the two copies.
Question 1 of 7In React 19, what is a fiber?
Choose an answer to see the explanation.
Question 2 of 7A fiber has
tag: 5andtype: "ul". What does itsstateNodehold?Choose an answer to see the explanation.
Question 3 of 7A
<ul>has three<li>children. Which links does React set between them?Choose an answer to see the explanation.
Question 4 of 7What is the FiberRoot?
Choose an answer to see the explanation.
Question 5 of 7After the commit of the third render, how many fibers can one
<strong>element have?Choose an answer to see the explanation.
Question 6 of 7What does this code print? It uses the same values as React's
PlacementandUpdateflags.Read the code, then predictlet flags = 0b110; flags &= ~0b10; console.log(flags);Choose an answer to see the explanation.
Question 7 of 7A click calls
setCountinCartItem. Which fibers does React mark, and how?Choose an answer to see the explanation.
Key takeaways
- React keeps a fiber, a plain object, for every component and element, and reuses it from render to render.
tagsays what kind of fiber it is,typewhich component or element, andstateNodewhat it stands for: a DOM node, the FiberRoot, or nothing.- Fibers form a tree of links:
childis the first child,siblingthe next one, andreturnthe parent. memoizedPropsandmemoizedStatehold what the last render used. A function component's state is its list of hooks.- Each updated fiber has a twin, its
alternate. React renders into the work-in-progress tree, and the commit points the FiberRoot'scurrentat it. flagsrecord what the commit must do andlaneswhat updates are waiting. Thesubtreeandchildversions let React skip parts of the tree.
A fiber is React's reusable record of one piece of the tree, linked to its parent, first child and next sibling.
React renders into one copy of the tree while the other stays on screen.
Coming next: The work loop. It reads ReactFiberWorkLoop.js and follows React as it walks this tree one fiber at a time, and stops between fibers to let the browser draw.