DEV Community

ZeeshanAli-0704
ZeeshanAli-0704

Posted on

Fiber in React

Here’s a concrete, simple example of what a Fiber node “looks like” and how it links to others, using a tiny DOM tree.

Example JSX

function App() {
  return (
    <div id="root-div" className="box">
      <span>Hello</span>
      <button>Click</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Conceptual Fiber nodes

  • React creates one Fiber per element/component. Each Fiber points to:
    • return: parent Fiber
    • child: first child Fiber
    • sibling: next child at the same level
    • stateNode: the real DOM node (for host elements like div/span/button) or instance (for class components)
    • alternate: the other version of this fiber (current vs work-in-progress) for double buffering

A rough, simplified shape (not exact internal names), for the initial mount:

Root fiber (HostRoot)

HostRootFiber = {
  tag: 'HostRoot',
  type: null,
  key: null,
  stateNode: rootContainer,     // e.g., the DOM container from ReactDOM.createRoot(...)
  pendingProps: null,
  memoizedProps: null,
  memoizedState: null,
  child: DivFiber,
  sibling: null,
  return: null,
  alternate: HostRootFiberWIP,   // work-in-progress version
  lanes: 0,
  flags: 0
}
Enter fullscreen mode Exit fullscreen mode

div fiber (the

)
DivFiber = {
  tag: 'HostComponent',
  type: 'div',
  key: null,
  pendingProps: { id: 'root-div', className: 'box', children: [...] },
  memoizedProps: { id: 'root-div', className: 'box', children: [...] }, // after commit
  stateNode: HTMLDivElement,     // real DOM node for this div
  child: SpanFiber,              // first child
  sibling: ButtonFiber,          // next child at same level
  return: HostRootFiber,         // parent
  alternate: DivFiberWIP,
  lanes: 0,
  flags: Placement | Update?     // flags for what to do in commit
}

span fiber (Hello)

SpanFiber = {
  tag: 'HostComponent',
  type: 'span',
  key: null,
  pendingProps: { children: 'Hello' },
  memoizedProps: { children: 'Hello' },
  stateNode: HTMLSpanElement,
  child: TextFiber,          // text nodes also get fibers
  sibling: ButtonFiber,
  return: DivFiber,
  alternate: SpanFiberWIP,
  lanes: 0,
  flags: 0
}

text fiber ("Hello")

TextFiber = {
  tag: 'HostText',
  type: null,
  key: null,
  pendingProps: 'Hello',
  memoizedProps: 'Hello',
  stateNode: TextNode,       // real DOM Text node
  child: null,
  sibling: null,
  return: SpanFiber,
  alternate: TextFiberWIP,
  lanes: 0,
  flags: 0
}

button fiber (Click)

ButtonFiber = {
  tag: 'HostComponent',
  type: 'button',
  key: null,
  pendingProps: { children: 'Click' },
  memoizedProps: { children: 'Click' },
  stateNode: HTMLButtonElement,
  child: ButtonTextFiber,
  sibling: null,
  return: DivFiber,
  alternate: ButtonFiberWIP,
  lanes: 0,
  flags: 0
}

How child/sibling/return link the tree

  • HostRootFiber.child → DivFiber
  • DivFiber.child → SpanFiber
  • SpanFiber.sibling → ButtonFiber
  • Each child’s return → DivFiber This creates a linked structure that React can traverse depth-first.

What happens on an update (e.g., className changes)

// Old: <div id="root-div" className="box">
function App() {
  return (
    <div id="root-div" className="box large">
      <span>Hello</span>
      <button>Click</button>
    </div>
  );
}
  • Render (reconciliation) phase:
    • React builds a work-in-progress (WIP) fiber tree using the old fibers via alternate pointers.
    • For the div:
    • Same type ('div') → update in place.
    • pendingProps becomes { id: 'root-div', className: 'box large', ... }.
    • React marks the fiber with an Update flag because props changed.
    • Children (span/button) are same type and keys → reused; no remount.
  • Commit phase:
    • React reads flags and applies minimal DOM changes:
    • For DivFiber, it updates the DOM node’s className from "box" to "box large".
    • memoizedProps on the DivFiber become the new props.
    • The WIP tree becomes current by swapping alternate pointers.

Keys in lists (quick note)

  • If we had children like items.map(item => {item.name}), the key sits on each child fiber.
  • During reconciliation, keys let React match old and new children in O(n) and decide which fibers to move, update, or delete without losing state.

Why this structure matters

  • Pointers (child/sibling/return) let React pause/resume work and traverse efficiently.
  • The alternate pointer enables double buffering: prepare updates off-screen, then commit atomically.
  • Flags tell the commit phase exactly what DOM mutations to perform (Placement, Update, Deletion), minimizing work.

If you want, I can expand this with a component fiber (function component) example showing where hooks state lives and how updates enqueue on a fiber.

Top comments (0)