Home / Articles

React Rendering Explained: State Updates to Screen Pixels

How React's render, reconciliation and commit phases connect to browser rendering: layout, paint and composite, from a state update to actual screen pixels.

Publisher: REACTAPP.TOP17 min read4015 words

This article is published in English.

Sources: Rendering Lifecycle in React: From State Update to Pixels on the Screen

When working with React, it's common to hear the phrase "the component re-rendered." But what does rendering actually mean under the hood?

A few natural questions come up here. Does React push updates to the DOM the moment state changes? Does a state change force every single component to re-render? And when React re-renders something, does that mean the browser immediately redraws the whole page? None of these are true, as it turns out.

To really grasp the rendering lifecycle, you need to understand two separate systems working together:

React's own rendering lifecycle, and the browser's rendering pipeline.

Once you see how these two systems connect, ideas like reconciliation, the commit phase, useEffect, useLayoutEffect, re-rendering, and memoization stop feeling like magic and start making sense.

1. The Big Picture

At a high level, the flow looks like this:

User Interaction
      │
      ▼
State / Props Change
      │
      ▼
┌──────────────────────┐
│     React Render     │
│                      │
│  Component executes  │
│  New UI is returned  │
└──────────┬───────────┘
           │
           ▼
     Reconciliation
           │
           ▼
┌──────────────────────┐
│     Commit Phase     │
│                      │
│  Update real DOM     │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────────┐
│    Browser Rendering     │
│                          │
│ Style → Layout → Paint   │
│           → Composite    │
└────────────┬─────────────┘
             │
             ▼
           Screen

The key takeaway is this:

React figures out what the UI should look like and which DOM changes are needed. The browser then takes those DOM changes and turns them into actual pixels on screen.

2. What Does "Rendering" Mean?

The term rendering gets used in more than one sense.

In the context of React, rendering usually means this:

React invokes your component function and works out what the resulting UI should be.

Consider this component:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button>
      Count: {count}
    </button>
  );
}

When React renders it, the function body runs:

Counter()
   ↓
returns React element
   ↓
<button>Count: 0</button>

This does not mean the browser has already painted the button on screen.

Before anything visual happens, React still has to decide whether the actual DOM even needs to change.

In short:

React Render
     ≠
DOM Update
     ≠
Browser Paint

Rendering and updating the DOM are two distinct operations.

3. Browser Rendering Lifecycle

Before diving into React's internals, it helps to understand what the browser itself does.

Imagine you have this markup:

<h1 class="title">
  Hello World
</h1>

Paired with this stylesheet:

.title {
  color: red;
  font-size: 30px;
}

Before this content ever appears visually, the browser runs through a series of internal steps.

A simplified version of that pipeline looks like this:

HTML
 ↓
DOM
 ↓
CSS
 ↓
CSSOM
 ↓
Render Tree
 ↓
Layout
 ↓
Paint
 ↓
Composite
 ↓
Screen

Let's walk through each stage.

4. HTML → DOM

The browser reads the HTML and builds the DOM, short for Document Object Model.

Given this markup:

<body>
  <h1>Hello World</h1>
  <p>Welcome</p>
</body>

The browser builds a tree structure roughly like this:

Document
   │
   └── body
       ├── h1
       │   └── "Hello World"
       │
       └── p
           └── "Welcome"

JavaScript can then query and manipulate this structure directly:

document.querySelector("h1");

In essence, the DOM answers one question:

Which elements exist on the page?

5. CSS → CSSOM

Alongside the DOM, the browser also parses your CSS and builds the CSSOM, or CSS Object Model.

For example:

h1 {
  color: red;
  font-size: 30px;
}

From this, the browser works out which styles apply to which elements.

Conceptually, it looks like this:

h1
 ├── color: red
 └── font-size: 30px

The CSSOM answers a different question:

How should each element be visually styled?

6. DOM + CSSOM → Render Tree

Next, the browser merges the DOM and the CSSOM to produce a Render Tree.

There's a subtlety worth noting:

Not every node in the DOM ends up represented in the Render Tree.

For instance, take this markup:

<div class="hidden">
  Hello
</div>

Combined with this rule:

.hidden {
  display: none;
}

The div element is present in the DOM:

DOM
 └── div

But since it carries:

display: none;

it gets excluded from the visual render tree entirely.

7. Layout

Once the browser knows exactly which elements need to be shown, it works out their geometry.

This involves calculating things such as:

  • Width
  • Height
  • Position
  • Margins
  • Padding
  • Coordinates
  • How elements relate spatially to one another

Take this rule as an example:

.box {
  width: 200px;
  height: 100px;
  margin-left: 50px;
}

The browser has to produce concrete values along these lines:

x      = 50px
y      = ...
width  = 200px
height = 100px

This calculation step is known as Layout, and it's also frequently referred to as Reflow.

8. Paint

With layout finished, the browser has coordinates for every visible element.

The next step is drawing what those elements actually look like.

Consider this styling:

.box {
  background: blue;
  color: white;
  border: 2px solid black;
}

Painting covers things like:

  • Background
  • Text
  • Borders
  • Shadows
  • Images
  • Other visual elements

In sequence:

Layout
  ↓
Paint
  ↓
Pixels

9. Composite

Browsers today can split a page into several distinct layers.

For instance:

Layer 1 → Page background
Layer 2 → Header
Layer 3 → Content
Layer 4 → Modal

The browser's compositor then merges all these layers into the single image that ends up on screen.

Certain CSS properties, like:

transform
opacity

can typically be animated through compositing alone, sidestepping the heavier layout work that geometry-affecting properties require.

That's part of why animating transform tends to be the preferred approach.

10. React Rendering Lifecycle

With the browser side covered, let's turn to how React handles rendering.

A helpful mental model breaks it into four stages:

1. Trigger
2. Render
3. Reconciliation
4. Commit
5. Browser Rendering

Let's walk through each one.

11. Phase 1 — Trigger

Before anything else happens, React needs a signal that says:

"The UI might need updating."

This signal is known as a render trigger.

Typical triggers include:

1. State update: setCount(10);

2. Props change: <Child count={count} />

When a parent component re-renders with a new value for count, its child may pick up new props as a result.

3. Context update: useContext(MyContext);

Whenever the value read from context changes, components consuming it may need to update.

4. External store update: Libraries and APIs can also trigger React updates.

Examples include:

Redux
Zustand
useSyncExternalStore

Mounting an app for the first time also counts as a trigger.

12. Phase 2 — Render

Once React picks up an update, it moves into the render phase, where it runs components to figure out the new UI.

Take this example:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

At the start, count is 0. React calls Counter() and produces:

<button>
  Count: 0
</button>

Once the button is clicked and this runs:

setCount(c => c + 1);

count becomes 1, and React re-executes the component, yielding:

<button>
  Count: 1
</button>

13. Important: Render Does NOT Mean DOM Update

This is a critical distinction in React's model.

When React runs this function:

function Counter() {
  console.log("Counter rendered");

  return <button>Count: 1</button>;
}

it's simply computing what the UI should look like. It does not automatically imply that the DOM changed or that the browser repainted anything.

The real sequence looks like:

Component executes
       ↓
React creates new element representation
       ↓
Reconciliation
       ↓
Determine required DOM changes
       ↓
Commit changes

Which leads to a key rule:

Rendering a component doesn't automatically translate into a DOM update.

14. Phase 3 — Reconciliation

At this stage, React compares the old and new UI to figure out exactly what's different.

Say the previous tree looked like:

button
 └── "Count: 0"

And the new tree looks like:

button
 └── "Count: 1"

React can figure out:

button → same
text   → changed

So it only needs to touch the part of the DOM that actually changed:

button.textContent = "Count: 1";

The whole point is to avoid tearing down and rebuilding the entire DOM tree when only a small piece has changed.

15. Keys and Reconciliation

Keys matter most when React is dealing with lists.

For example:

items.map(item => (
  <li key={item.id}>
    {item.name}
  </li>
))

React relies on keys to track which element is which across renders. Say you start with:

Before:

A
B
C

and end up with:

After:

A
C

Given stable keys, React can correctly infer:

A → same
B → removed
C → same

This gives React much more reliable information to work with during reconciliation.

Avoid relying on array indexes as keys whenever list items might get reordered, added, or removed.

16. Phase 4 — Commit

Once React has worked out what needs to change, it moves into the commit phase, where it actually applies those changes to the real DOM.

Picture a button element whose label goes from showing a count of zero to showing a count of one once this phase completes.

At this point, React's internal tree and the live DOM finally match up.

The commit phase is a separate step from the render phase:

Render Phase
    ↓
Calculate what should change

Commit Phase
    ↓
Actually apply DOM changes

17. Render Phase vs Commit Phase

Understanding this split is one of the more common things interviewers probe.

Render Phase

During this phase, React:

  • Runs component functions
  • Works out the next version of the UI
  • Performs reconciliation
  • Figures out what needs to change
Component
   ↓
React elements
   ↓
Reconciliation

Commit Phase

During this phase, React:

  • Applies the actual DOM mutations
  • Updates any refs
  • Runs the layout effects tied to this update
React result
    ↓
Real DOM

Here's a simplified picture of the flow:

             React
               │
               ▼
        ┌──────────────┐
        │ Render Phase │
        └──────┬───────┘
               │
               ▼
        ┌───────────────┐
        │ Commit Phase  │
        └──────┬────────┘
               │
               ▼
            Real DOM

18. What Happens After Commit?

Once the commit phase finishes, the browser takes over and notices that the DOM has changed.

Depending on what changed, the browser may need to run through several stages:

DOM Changes
    ↓
Style Calculation
    ↓
Layout
    ↓
Paint
    ↓
Composite

Not every change requires all of these stages to do meaningful work — a change might only need a fresh paint, for instance, without needing a full layout recalculation.

This is the exact point where React's own lifecycle hands off to the browser's rendering pipeline described earlier.

19. useLayoutEffect

useLayoutEffect fires after React has committed its DOM updates but before the browser gets a chance to paint the new visuals.

The sequence looks like this:

React Render
     ↓
Commit
     ↓
DOM updated
     ↓
useLayoutEffect
     ↓
Browser Paint
function Tooltip() {
  const ref = useRef(null);

  useLayoutEffect(() => {
    const rect = ref.current.getBoundingClientRect();

    console.log(rect.width);
  }, []);

  return (
    <div ref={ref}>
      Tooltip
    </div>
  );
}

This hook is the right tool when you need to:

  • Measure elements in the DOM
  • Read current layout values
  • Apply visual adjustments synchronously, before the user sees anything

One caution: if the work inside useLayoutEffect is heavy, it can hold up the paint and make the UI feel sluggish.

20. useEffect

useEffect, by contrast, is meant for effects that don't have to block the browser's paint.

useEffect(() => {
  fetch("/api/products");
}, []);
Render
   ↓
Commit
   ↓
Browser Paint
   ↓
useEffect

The actual scheduling details are a bit more subtle than this, but the key practical rule is:

Reach for useLayoutEffect only when something must happen before the browser paints; use useEffect for anything that's fine running after the UI is already visible.

21. Complete Example

import { useEffect, useLayoutEffect, useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  console.log("Render");

  useLayoutEffect(() => {
    console.log("Layout Effect");
  });

  useEffect(() => {
    console.log("Effect");
  });

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

Conceptually, the sequence of events looks like this:

User clicks button
        ↓
setCount()
        ↓
State update scheduled
        ↓
Counter() executes
        ↓
"Render"
        ↓
React reconciles
        ↓
React commits DOM update
        ↓
"Layout Effect"
        ↓
Browser paints updated UI
        ↓
"Effect"

22. Does a Parent Re-render Cause the Child to Render?

Take this example:

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>
        {count}
      </button>

      <Child />
    </>
  );
}

When count updates:

Parent renders
       ↓
Child is encountered
       ↓
Child may render as part of reconciliation

So,

Parent state update
      ↓
Parent render
      ↓
Child can render

This behavior is part of the reason React.memo exists as an optimization tool.

23. React.memo

Suppose you have:

const Child = React.memo(function Child() {
  console.log("Child rendered");

  return <p>Hello</p>;
});

If the parent re-renders but the props passed to the child stay referentially identical, React is able to skip re-rendering that memoized child entirely.

Here's the idea visually:

Parent update
     ↓
Parent renders
     ↓
React.memo checks Child props
     ↓
Props unchanged
     ↓
Skip Child render

Keep in mind, though:

React.memo gives you a performance shortcut — it doesn't promise a component will never render again.

Context changes, internal state, and other triggers can still force it to update regardless of memoization.

24. useMemo and useCallback

These two hooks are frequently misunderstood.

useMemo: It memoizes a calculated value.

const result = useMemo(() => expensiveCalculation(data), [data]);

useCallback: It memoizes a function reference.

const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);

Both are genuinely helpful in situations where referential equality matters for skipping repeated work — most notably when passing props to memoized child components.

That said, sprinkling them everywhere by default tends to add complexity to your code without any real performance payoff.

25. A React Render Isn't the Same as a Browser Paint

This point is worth calling out explicitly.

Imagine the following component:

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>
        {count}
      </button>

      <p>Hello</p>
    </div>
  );
}

When count changes:

App renders
     ↓
New React tree created
     ↓
Reconciliation
     ↓
Only count text differs
     ↓
Commit small DOM update
     ↓
Browser rendering

React won't necessarily rebuild this markup from the ground up:

<div>
  <button>...</button>
  <p>Hello</p>
</div>

26. Virtual DOM — Getting the Mental Model Right

A common claim you'll come across is that React relies on a Virtual DOM to speed up the browser. That's an oversimplification.

What React actually does is keep an internal description of the UI in memory and use reconciliation to figure out what has changed.

The browser itself has no concept of a Virtual DOM. It only works with things like HTML, the DOM, CSS, and ultimately pixels.

Conceptually, the flow looks like this:

React Component
      ↓
React Element Tree
      ↓
Reconciliation
      ↓
DOM Mutations
      ↓
Browser
      ↓
Pixels

The Virtual DOM is never something the browser actually renders.

27. Re-render vs Repaint

These are two distinct concepts that shouldn't be conflated.

React Re-render

This is when a component function runs again — essentially, Component() executes.

Browser Repaint

This is when the browser redraws pixels on screen because some visual property changed — the Paint step.

So keep in mind: a React re-render is not equivalent to a browser repaint.

It's entirely possible for a component to render again yet output an identical DOM structure. Conversely, the browser can repaint for reasons that have nothing to do with any React component rendering.

28. Reflow vs Repaint

Take this example: element.style.width = "500px";

Changing an element's width typically forces a layout recalculation:

Style
 ↓
Layout
 ↓
Paint
 ↓
Composite

Now compare it to: element.style.backgroundColor = "red";

Here, the browser generally doesn't need to redo geometry calculations:

Style
 ↓
Paint
 ↓
Composite

And for certain transform or opacity changes, the browser may only need to:

Composite

Understanding this difference matters a lot when you're optimizing animations or interfaces with heavy rendering demands.

29. Applying This to a Real React Scenario

Picture a typical e-commerce product page structured like this:

Product Page
 ├── Navbar
 ├── Product Gallery
 ├── Product Information
 ├── Add To Cart
 ├── Reviews
 └── Recommendations

Say a user clicks the Add to Cart button, which updates the cart count.

Conceptually, the sequence of events might look like:

User clicks "Add to Cart"
           ↓
Cart state updates
           ↓
React schedules update
           ↓
Relevant components render
           ↓
React reconciles new tree
           ↓
Only required DOM changes are committed
           ↓
Browser performs necessary rendering work
           ↓
Updated cart count appears

In a well-structured app, this cart count update shouldn't force the entire UI to be rebuilt unnecessarily. This is exactly why decisions around component boundaries, where state lives, memoization, and overall rendering architecture carry real weight.

30. When Costly Rendering Becomes a Performance Problem

function ProductList({ products }) {
  return products.map(product => (
    <ExpensiveProductCard
      key={product.id}
      product={product}
    />
  ));
}

Suppose this list renders 10,000 products, and some unrelated state change triggers wasted work across a large number of components — performance can degrade noticeably.

Common mitigation techniques include:

React.memo
      +
Stable props
      +
useMemo/useCallback where justified
      +
Virtualization
      +
Pagination
      +
Code splitting

For particularly large lists, virtualization tends to be especially effective:

10000 items
     ↓
Only visible ~20-50 items rendered

31. How Rendering Interacts with Concurrent React

Newer versions of React include more advanced scheduling capabilities. React can assign priority to different updates and, when it makes sense, pause or postpone rendering work.

The key mental model to hold onto is:

Render work
    ↓
Can be scheduled
    ↓
Can be interrupted in modern React
    ↓
Eventually committed

This is exactly why side effects shouldn't live inside a component's render logic:

function Component() {
  fetch("/api/data"); // ❌ Side effect during render

  return <div>Hello</div>;
}

Instead, use an effect hook or a suitable data-fetching pattern. Ideally, the render phase should stay pure.

32. The Case for Pure Rendering

Conceptually, a React component should behave like a pure function:

UI = f(props, state)

Given the same props and state, it should always describe the same UI. For instance:

function Greeting({ name }) {
  return <h1>Hello {name}</h1>;
}

This behavior is predictable and safe.

By contrast, doing something like this inside render is problematic:

function Component() {
  localStorage.setItem("x", "1"); // ❌
  fetch("/api");                  // ❌

  return <div>Hello</div>;
}

This injects side effects into a phase that React might run more than once, or schedule in unexpected ways.

Side effects belong elsewhere — inside effects, event handlers, or the data-fetching mechanisms your framework provides.

33. Rendering Lifecycle — Complete Mental Model

                    USER ACTION
                         │
                         ▼
               State / Props Update
                         │
                         ▼
                React Schedules Work
                         │
                         ▼
                 ┌──────────────┐
                 │    RENDER    │
                 │              │
                 │ Components   │
                 │ execute      │
                 └──────┬───────┘
                        │
                        ▼
                 Reconciliation
                        │
                        ▼
                 ┌──────────────┐
                 │    COMMIT    │
                 │              │
                 │ DOM mutation │
                 └──────┬───────┘
                        │
                        ▼
                 useLayoutEffect
                        │
                        ▼
              Browser Rendering
                        │
                        ▼
                 Style Calculation
                        │
                        ▼
                     Layout
                        │
                        ▼
                      Paint
                        │
                        ▼
                   Composite
                        │
                        ▼
                      SCREEN
                        │
                        ▼
                   useEffect

Common Interview Questions

Q1. Does a state update immediately update the DOM?

No. A state update merely schedules work for React to do. React renders the component, reconciles the output against what came before, and only then commits whatever DOM changes are actually required.

setState
  ↓
Render
  ↓
Reconciliation
  ↓
Commit
  ↓
DOM

Q2. Does every render lead to a DOM update?

No. React can render a component and still conclude that the resulting UI description is identical to what's already on screen, in which case it skips touching the DOM.

Render
  ↓
Compare
  ↓
No meaningful DOM difference
  ↓
No DOM mutation

Q3. Does re-rendering in React force the browser to repaint?

No. Rendering in React and painting in the browser are two independent processes.

React Render ≠ Browser Paint

Q4. What does reconciliation mean?

Reconciliation is how React compares the freshly computed UI tree against the previous one to figure out exactly what needs to change.

Q5. What happens during the commit phase?

During commit, React applies the required changes to the host environment — for example, updating the actual DOM nodes in a browser.

Q6. How do useEffect and useLayoutEffect differ?

A simple way to remember it:

useLayoutEffect
→ after DOM commit, before paint

useEffect
→ after commit and generally after paint

Reach for useLayoutEffect when you need to measure or adjust layout synchronously before the user sees the new frame. Use useEffect for the majority of side effects that don't need to block painting.

Q7. What triggers a component to render?

Typical triggers are: an initial mount, a state update, a parent re-rendering, a change in props, a context update, or an update from an external store.

Q8. How can rendering be optimized? [IMP]

Rather than sprinkling useMemo everywhere by default, first pinpoint where the actual bottleneck is. Then consider these approaches:

1. Keep state close to where it's used

Avoid a structure where state sits at the top and every descendant depends on it:

App
 ↓
Everything depends on state

prefer:

Feature
 ↓
Local state

favoring a narrower placement instead, when global state isn't actually necessary.

2. Memoize expensive components selectively

const ProductCard = React.memo(ProductCardComponent);

3. Watch out for object creation that breaks memoization

Instead of repeatedly creating a fresh object inline like this:

<Child config={{ theme: "dark" }} />

you may need a stable reference if Child is wrapped in React.memo and identity matters — but don't fix this until it's actually a problem.

4. Virtualize long lists

Instead of rendering all 10,000 DOM nodes at once, render roughly only the ones currently visible.

5. Defer loading of costly features

const AdminDashboard = lazy(() => import("./AdminDashboard"));

This shrinks the amount of JavaScript that must be downloaded and executed up front.

6. Favor browser-friendly rendering techniques

Animate with cheap properties such as transform and opacity where possible, and avoid forcing expensive layout recalculations on every frame.