Kartik Goel

๐Ÿ”„ Starting diving into React Internals

June 15, 2025 ยท 8 min read

I've been re-reading JSER blogs to delve deeper into React's internals, particularly focusing on how React schedules and processes updates. In this post, I'll walk through the entire rendering process, from the initial trigger to the final paint on screen.

This is a technical deep dive into React's internals. If you're new to React, you might want to start with the official documentation first.

1. The Trigger Stage

Every React update begins with a trigger. There are two primary ways an update gets triggered in React:

  • ReactDomRoot.render() โ€” This happens during the initial mount or first render of your application
  • setState โ€” This occurs when state changes in a component

Both of these methods internally call scheduleUpdateOnFiber, which is essentially the first internal function called on any update.

Internal React Flow
// Simplified internal flow
function setState(newState) {
  // ...
  scheduleUpdateOnFiber(fiber);
}

function render(element) {
  // ...
  scheduleUpdateOnFiber(rootFiber);
}

What is Fiber?

Based on the previous information, it's clear that updates are scheduled on something called a "Fiber." But what exactly is a Fiber?

In layman's terms, Fiber is an internal React object that holds a component's state, props, and other metadata. It helps React determine what changes to make to the DOM. You can think of it as a powerful version of the virtual DOM node.

The next part of the Trigger stage is ensureRootIsScheduled, which ensures that an update is scheduled on the React root. After this, control passes to the Scheduler stage.

2. The Schedule Stage

This is where React decides when to perform the actual work of updating the DOM.

ensureRootIsScheduled() โ†’ scheduleCallback()

Here, the update task is passed to the scheduler, which acts like a smart background manager. It queues updates based on priority and calls workLoop() when it's time to start processing.

workLoop()

This is a loop that processes all scheduled updates by traversing through the fiber nodes. During this process, React builds a new tree of fiber nodes that includes all the updates.

Why does React need a scheduler?

Not all updates are equally urgent. React uses the Scheduler to prioritize tasks based on their importance and breaks larger tasks into smaller ones in concurrent mode. This helps maintain an appropriate frame rate and keeps the UI responsive, even during complex updates.

3. The Render Stage

Once a task is picked up from the scheduler, React needs to compute the changes. It walks through the fiber nodes and calculates what needs to change in the DOM.

At this point, React calls one of two functions:

  • renderRootConcurrent() โ€” The default in concurrent mode
  • renderRootSync() โ€” Used in legacy mode (synchronous and blocking)

It's important to note that during this stage, nothing is actually changed in the DOM yet. React is only calculating what needs to be changed, essentially creating a to-do list of DOM updates.

Render Phase (Simplified)
// Simplified render phase
function renderRoot(root, isSync) {
  if (isSync) {
    return renderRootSync(root);
  }
  return renderRootConcurrent(root);
}

// This builds the effect list - a list of DOM updates to be applied later
function renderRootConcurrent(root) {
  // Walk the fiber tree and compute changes
  // ...
  return finishedWork; // The fiber tree with pending effects
}

4. The Commit Stage

This is where React actually applies the changes to the DOM. The commit stage is initiated by calling commitRoot().

The commit stage is often divided into three phases:

Before Mutation Phase

In this phase, React performs tasks that might need the old DOM state before any mutations occur. This includes capturing current DOM state for later comparison.

Mutation Phase

This is where the actual DOM updates happen, such as removing, updating, or inserting nodes. It's important to note that even though the DOM is being updated, the browser hasn't painted these changes to the screen yet.

Layout Phase

During this phase, useLayoutEffect hooks are executed. These are synchronous and block the browser from painting, which is why they're ideal for tasks that need to run on the final DOM update before the screen is painted (like measuring DOM elements or smooth transitions).

After the Commit Stage

After the commit stage completes, the browser paints the screen, allowing users to see the updated UI. Following this, useEffect hooks are executed. Unlike useLayoutEffect, useEffect runs after the browser has painted and is non-blocking, which is why it's recommended for most side effects.

Conclusion

Understanding React's internal rendering process helps us write more efficient and performant React applications. By knowing how React schedules, renders, and commits updates, we can make better decisions about when to use different hooks like useEffect vs useLayoutEffect, and how to structure our components for optimal performance.

In future posts, I'll dive even deeper into specific parts of this process, particularly focusing on the Scheduler and how React prioritizes updates in concurrent mode.

If you found this helpful, feel free to like and share! And let me know in the comments if there are specific aspects of React's internals you'd like me to explore next.

Built with v0