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 percreateRoot().HostRootfiber — 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)