cf.completefrontend
Open lab My progress
THE REACT FIELD GUIDE

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.

By the end, you can
  • 01
    Read a fiber's fieldsSay what tag, type, key, stateNode, memoizedProps and memoizedState hold for a component and for a DOM element.
  • 02
    Walk the fiber treeMove through the tree with child, sibling and return, and explain why React links fibers this way instead of using arrays.
  • 03
    Explain 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.

A button finds its own fiberPop out in the code editor (opens in a new tab)JSX
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>  );}
ResultLoading…

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>.

Looking is fine, using is not

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:

The fiber key's name, ReactDOMComponentTree.js lines 43 and 44JavaScript
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.

Left, a page window holding a button element. Middle, a crate named fiber holding four cards: tag 5, type button, stateNode, and return. An arrow goes from stateNode to the button on the page. An arrow goes from return to a card on the right, the section's fiber.page<button>the DOM elementtag: 5type: "button"stateNodereturnfibersection's fiberits parent
The button's fiber is a plain object. 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>.
Real-life analogyJob cards in a workshop

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
The main idea

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:

FiberNode in ReactFiber.js, lines 134 to 146JavaScript
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:

  1. Lines 1 to 7 take four values: the tag, the props for this render, the key, and a mode (bits such as "this tree is in StrictMode"). this: $FlowFixMe is only a note for Flow, not a value.
  2. Line 8 starts the group of fields that say what this piece is.
  3. Line 9 stores the tag: a number for the kind of fiber, such as a function component or a DOM element.
  4. Line 10 stores the key you wrote in JSX, like key="tea", or null.
  5. Lines 11 and 12 start elementType and type empty. The function that makes a fiber from an element fills them in: the component function, like CartItem, 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 with lazy.
  6. 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:

The first work tags, ReactWorkTags.js lines 44 to 51JavaScript
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.

The work tags you meet most
TagName in ReactWorkTags.jsWhat you write to get itWhat stateNode holds
0FunctionComponentA function component, like CartItem.null. A function component has no object of its own.
3HostRootNothing: createRoot makes it.The FiberRoot, React's object for the whole root.
5HostComponentA DOM element, like <li> or <button>.The real DOM element on the page.
6HostTextText next to other children, like Items: {count}.The real text node on the page.
7FragmentA <Fragment> or <> that isn't the whole return value.null.
8Mode<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.

A fiber inspector: click anything, then climbPop out in the code editor (opens in a new tab)JSX
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>}    </>  );}
ResultLoading…

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:

  1. li: tag 5, HostComponent. Its stateNode is the <li> on the page.
  2. CartItem: tag 0, FunctionComponent, key tea, and stateNode null. A function component has no DOM node of its own; the li it returns is its child.
  3. ul, then section, then App: two elements and your component.
  4. Mode, tag 8: the <StrictMode> that every live example's main.jsx wraps around App.
  5. HostRoot, tag 3: the top of the tree. Its stateNode is the FiberRoot, and its return is null, 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.

A fiber's fields, by the question they answer
The questionThe fieldsWhat they hold
What is it?tag, type, key, elementTypeThe kind of fiber, the component or tag name, and the key you gave it.
What does it stand for on the page?stateNodeThe DOM node, the FiberRoot, or a class instance. null for function components.
Where is it in the tree?return, child, sibling, indexLinks to its parent, its first child, and its next sibling, and its position.
What does it remember?pendingProps, memoizedProps, memoizedState, updateQueueThe props for this render and the last one, the state (hooks for a function), and waiting updates.
What must happen next?flags, subtreeFlags, deletionsChanges the commit must make here, and a summary of the ones below.
Does it have work waiting?lanes, childLanesUpdates waiting on this fiber, and on any fiber below it, as bits.
Where is its other copy?alternateThe same fiber in the other tree: current or work-in-progress.

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:

What a fiber remembers, ReactInternalTypes.js lines 142 to 150JavaScript
  // 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,
  1. Lines 1 and 2: pendingProps are the props for the render in progress, the "input" of this piece of work.
  2. Line 3: memoizedProps are the props of the last finished render. "Memoized" means remembered. Comparing the two is how React notices that a parent passed something new.
  3. Lines 5 and 6: updateQueue holds waiting work. For the root, it holds the waiting root.render calls. A function component keeps its waiting state updates in each hook instead, and uses updateQueue for its effects.
  4. Lines 8 and 9: memoizedState is the state the last render used. For a function component, that's its hooks.

Press Add one twice, then Read my fiber.

A component reads its props and hooks from its fiberPop out in the code editor (opens in a new tab)JSX
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>  );}
ResultLoading…

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 follows next from 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:

The first FiberRoot fields, ReactInternalTypes.js lines 212 to 221JavaScript
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,
  1. Line 3: its own tag, a root tag, not a work tag. createRoot always makes a concurrent root.
  2. Line 6: containerInfo, the DOM element you passed to createRoot: your <div id="root">.
  3. Line 8: a field only some renderers use.
  4. 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:

createFiberRoot links the two, ReactFiberRoot.js lines 208 to 212JavaScript
  // 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.

From the #root div to the HostRoot fiber and the FiberRootPop out in the code editor (opens in a new tab)JSX
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>    </>  );}
ResultLoading…

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 is App.
  • 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:

createWorkInProgress in ReactFiber.js (shortened)JavaScript
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;
  1. Lines 1 and 2 take the current fiber and look at its alternate.
  2. 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.
  3. Lines 19 and 20 link the two, both ways.
  4. 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.
  5. 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.
At the top, a card named FiberRoot. An arrow labeled current goes from it to the HostRoot card of the left tree. The left tree, labeled current, on screen, has HostRoot, App and strong cards linked downward. The right tree, labeled work-in-progress, has the same three cards. Between each pair, two arrows labeled alternate point in both directions.FiberRootcurrentHostRootHostRootalternateAppAppalternate<strong><strong>alternatecurrent: on screenwork-in-progress
Every fiber that has been updated has a twin in the other tree, and the two point at each other through 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.

One <strong>, two fibers taking turnsPop out in the code editor (opens in a new tab)JSX
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>  );}
ResultLoading…

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:

  1. Before any click: the fiber remembers 0, and there is no alternate yet. Nothing has been updated.
  2. 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.
  3. 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 createWorkInProgress do.
  4. 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 commit swaps the trees, ReactFiberWorkLoop.js lines 4047 to 4051JavaScript
  // 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:

The last fields of FiberNode, ReactFiber.js lines 165 to 173JavaScript
  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  this.alternate = null;
  1. Line 2: flags, the changes the commit must make for this fiber.
  2. Line 3: subtreeFlags, every flag of every fiber below it, collected together.
  3. Line 4: deletions, the child fibers to remove.
  4. Lines 6 and 7: lanes, the updates waiting on this fiber, and childLanes, the ones waiting anywhere below it.
  5. 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:

The first flags, ReactFiberFlags.js lines 17 to 28JavaScript
// 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:

Turning flag bits on and offPop out in the code editor (opens in a new tab)JavaScript
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.

Two columns of the same four fibers: App, ul, CartItem, li, with arrows pointing up. Left column, an update waits: CartItem has lanes SyncLane, and ul and App have childLanes SyncLane. Right column, a change to make: li has flags Update, and CartItem, ul and App have subtreeFlags Update.an update waits: lanesa change to make: flagsAppchildLanes: SyncLaneAppsubtreeFlags: UpdateulchildLanes: SyncLaneulsubtreeFlags: UpdateCartItemlanes: SyncLaneCartItemsubtreeFlags: Updatelino lanesliflags: Update
Left: a click calls 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:

The first lanes, ReactFiberLane.js lines 43 to 54JavaScript
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.

The lane a set function marks on App's fiberPop out in the code editor (opens in a new tab)JSX
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>    </>  );}
ResultLoading…

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.

Find the components above anything you clickPop out in the code editor (opens in a new tab)JSX
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>  );}
ResultLoading…

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.

Use React DevTools for this in a real app

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.

Which field 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?
Try it yourself
0 of 11 correct

Put each question under the group of fiber fields that answers it.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

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, and createWorkInProgress reuses 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 with return.
  • "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 current points 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.

A React element and a fiber
The questionReact elementFiber
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

Exercise 1 · Warm-upTwo tags

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>
  );
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upIndex and key

    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>
        </>
      );
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeList the children by walking links

      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>    </>  );}
      ResultLoading…

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeWhen does the alternate appear?

        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>
          );
        }

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeCount every fiber without recursion

          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>  );}
          ResultLoading…

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeWhich cart item was clicked?

            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>    </>  );}
            ResultLoading…

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, picture the fiber tree: which fiber, which field, and which of the two copies.

              The fiber quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In React 19, what is a fiber?

                Choose an answer to see the explanation.

              2. Question 2 of 7A fiber has tag: 5 and type: "ul". What does its stateNode hold?

                Choose an answer to see the explanation.

              3. Question 3 of 7A <ul> has three <li> children. Which links does React set between them?

                Choose an answer to see the explanation.

              4. Question 4 of 7What is the FiberRoot?

                Choose an answer to see the explanation.

              5. 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.

              6. Question 6 of 7What does this code print? It uses the same values as React's Placement and Update flags.

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                let flags = 0b110;
                flags &= ~0b10;
                console.log(flags);

                Choose an answer to see the explanation.

              7. Question 7 of 7A click calls setCount in CartItem. 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.
              • tag says what kind of fiber it is, type which component or element, and stateNode what it stands for: a DOM node, the FiberRoot, or nothing.
              • Fibers form a tree of links: child is the first child, sibling the next one, and return the parent.
              • memoizedProps and memoizedState hold 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's current at it.
              • flags record what the commit must do and lanes what updates are waiting. The subtree and child versions 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.

              CompleteFrontend Clear concepts. Working examples.