⚛ The Course  /  Module 1 · The Work Tree (Fibers) · 1.1
Module 1 · The Work Tree (Fibers)

1.1 What a Fiber is, and why work is a tree

When you write JSX, you hand React a fresh description of the screen on every render. But React also has to remember things between renders — your useState values, which real DOM nodes already exist, what work is still pending — and it has to be able to stop halfway through and pick up later. A plain element can't hold any of that. So what does? The answer is the Fiber, and it is the single object the rest of this course revolves around.

One React element ≈ one Fiber

Start with a component you might actually write:

function App() {
  return (
    <>
      <Header />
      <Content>
        <Sidebar />
        <Main />
      </Content>
      <Footer />
    </>
  );
}

Every one of those elements — App, the fragment, Header, Content, Sidebar, Main, Footer — becomes a Fiber node when React renders.

So the rule of thumb is simple: one element, roughly one Fiber. The element is the throwaway description you produced this render; the Fiber is the durable object React builds from it and keeps alive across renders. We'll sharpen the word "roughly" in later lessons (keys, lists, and bailouts bend it a little), but for now it's a faithful one-to-one picture.

A Fiber is both the unit of work and the unit of memory

The Fiber wears two hats, and this is the whole reason it exists.

As the unit of work, a Fiber is one item on React's to-do list. React walks the fibers one at a time, doing a little work on each — running your component, comparing props, marking what changed. Because the work is split into per-fiber chunks, React can pause between fibers and hand the browser back control, then resume right where it left off. (That pause-and-resume is Module 4's job; here, just notice that a Fiber is the unit that makes it possible.)

As the unit of memory, a Fiber is where React stores the things that must outlive a single render: this component's state, its pending effects, and the link to its real DOM node. Your elements are recreated from scratch every render and thrown away; the Fiber is not. So anything React needs to remember has to live on the Fiber.

Where state and the DOM actually live

This is the part worth burning into memory: state, effects, and DOM references all hang off the fiber. The same object shape carries a different payload depending on what kind of thing the fiber represents.

A function component's state isn't kept in some separate store — it lives on the fiber, in memoizedState, as a small linked list of hooks (one node per useState/useEffect/etc., in call order):

The real code

{
  tag: 0,            // FunctionComponent
  type: Profile,     // your function
  stateNode: null,   // function components have NO instance

  // the hook chain — one node per hook, in call order:
  memoizedState: {
    memoizedState: 5,                 // useState value
    queue: { dispatch: setCount },
    next: {
      memoizedState: 'dark',          // useContext(ThemeContext)
      next: { memoizedState: {name:'Alice'}, next: null }
    }
  },
  lanes: 2,          // has scheduled work
  flags: 4,          // Update
}

A DOM element's fiber is different: there are no hooks, and instead stateNode points straight at the real browser node:

The real code

{
  tag: 5,                              // HostComponent
  type: "div",
  stateNode: /* the real <div class="container"> */ ,
  pendingProps: { className: "container", style: { color: 'red' } }
}

Notice the one field doing the heavy lifting in both: stateNode. It is the bridge between React's virtual world and the real one.

The two roots: FiberRootNode vs the HostRoot fiber

When you call ReactDOM.createRoot(document.getElementById('root')), React creates two distinct objects, and people constantly confuse them:

The real code

function createFiberRoot(containerInfo, ...) {
  // 1. FiberRootNode — NOT a fiber. It's the container / scheduler object.
  var root = new FiberRootNode(containerInfo, tag, hydrate, ...);

  // 2. HostRoot fiber — the TOP fiber in the fiber tree (tag = 3)
  var uninitializedFiber = createHostRootFiber(tag, isStrictMode);

  // 3. Link them together
  root.current = uninitializedFiber;     // FiberRootNode.current → HostRoot fiber
  uninitializedFiber.stateNode = root;   // HostRoot.stateNode → FiberRootNode
}
  • FiberRootNode — a plain object, not a fiber. It holds scheduler state (pendingLanes, callbackNode, finishedWork, containerInfo). Created once per createRoot().
  • HostRoot fiber — the actual top fiber of the tree (tag = 3). This is the node React clones at the start of every render.

They point at each other, which is exactly why they're easy to mix up: root.current takes you down to the HostRoot fiber, and that fiber's stateNode points back up to the FiberRootNode.

Fibers form a tree

A single Fiber isn't interesting on its own — the power comes from how they connect. The fibers built from your JSX hang together in a tree, rooted at the HostRoot fiber, which itself sits just under the (non-fiber) FiberRootNode.

  FiberRootNode (NOT a fiber)
  │  .containerInfo = <div id="root"> (real DOM)
  │  .current
  ▼
  HostRoot fiber (tag=3)               ← top fiber, .stateNode → FiberRootNode
  │  .child
  ▼
  App fiber (tag=0, FunctionComponent) ← .return → HostRoot
  │  .child
  ▼
  Fragment fiber (tag=7)               ← .return → App   (the <> </>)
  │  .child
  ▼
  Header fiber (tag=0)                 ← .return → Fragment
  │  .sibling
  ▼
  Content fiber (tag=0)                ← .return → Fragment  (NOT child of Header!)
  │  .child                │ .sibling
  ▼                        ▼
  Sidebar fiber (tag=0)    Footer fiber (tag=0)  ← .return → Fragment
  │  .sibling                                       (sibling of Content, NOT its child)
  ▼
  Main fiber (tag=0)                   ← .return → Content
      .sibling = null

For now, absorb just the shape: the FiberRootNode on top is not a fiber; the HostRoot fiber sits right below it; and your components hang underneath in a tree that mirrors your JSX. The exact wiring — those .child, .sibling, and .return arrows, and why React uses a linked structure instead of an array of children — is the entire subject of the next lesson, 1.2. Don't worry about the arrows yet.

The real code

You don't need to memorize the full Fiber object, but it's worth seeing once: every field here maps to something you already use in React. State, effects, scheduling, and the tree links all live on this one node.

Show the full FiberNode object
function FiberNode(tag, pendingProps, key, mode) {
  // ── Identity ──
  this.tag = tag;              // what KIND of fiber (0=FunctionComponent, 5=host DOM, 13=Suspense…)
  this.key = key;              // reconciliation key
  this.elementType = null;     // the element's type (your function/class, or 'div')
  this.type = null;            // resolved type
  this.stateNode = null;       // DOM node, or class instance, or FiberRootNode

  // ── Tree structure ──
  this.return = null;          // parent fiber
  this.child = null;           // first child
  this.sibling = null;         // next sibling
  this.index = 0;              // position among siblings

  // ── Ref ──
  this.ref = null;
  this.refCleanup = null;

  // ── Props & state ──
  this.pendingProps = pendingProps;  // incoming props (the input to this render)
  this.memoizedProps = null;         // props from the LAST render (for comparison)
  this.updateQueue = null;           // queue of pending updates
  this.memoizedState = null;         // last state — the HOOK CHAIN for function components
  this.dependencies = null;          // context subscriptions ← powers useContext

  // ── Mode ──
  this.mode = mode;            // Concurrent / Strict / etc.

  // ── Effects ──
  this.flags = 0;              // side-effect flags on THIS fiber (Placement, Update, …)
  this.subtreeFlags = 0;       // OR of all descendants' flags (lets React skip clean subtrees)
  this.deletions = null;       // children scheduled for removal

  // ── Lanes (scheduling) ──
  this.lanes = 0;              // work scheduled on THIS fiber
  this.childLanes = 0;         // work scheduled somewhere in its subtree

  // ── Double buffering ──
  this.alternate = null;       // link to this fiber's other copy (current ↔ workInProgress)
}

That tag on the first line is a small integer telling React how to process this fiber. A few you'll meet over and over:

var FunctionComponent = 0;
var ClassComponent    = 1;
var HostRoot          = 3;   // root of the tree
var HostComponent     = 5;   // a DOM element (div, span…)
var HostText          = 6;   // a text node
var Fragment          = 7;
var ContextConsumer   = 9;
var ContextProvider   = 10;
var Suspense          = 13;  // a Suspense boundary
var Offscreen         = 22;  // hidden subtree (Suspense fallback, Activity)
var Activity          = 31;  // Activity component (React 19)