Skip to main content

What causes a re-render?

Key idea

The Virtual DOM is the foundation on which React implements a component's lifecycle.

React does not work directly with the real DOM on every change. Instead:

  1. It builds a virtual tree (Virtual DOM) - JS objects describing the interface structure.
  2. When state changes (state, props), React creates a new version of the virtual tree.
  3. It compares it with the old version (the reconciliation process).
  4. It computes the minimal set of changes that need to be applied to the real DOM.
  5. It applies them in the commit phase.

And all lifecycle events happen around these actions.


How the Virtual DOM relates to lifecycle phases

Here is the relationship, step by step:

StageWhat React does with the Virtual DOMWhich methods / hooks are called
MountingReact creates the first virtual tree from the JSX componentsuseEffect (after commit), useLayoutEffect, componentDidMount
UpdatingReact creates a new virtual tree, compares it with the old one -> finds the differences (diffing)useEffect (cleanup + new), useLayoutEffect, componentDidUpdate
UnmountingReact removes the node from the virtual tree -> removes the corresponding DOM elementscleanup functions in useEffect, componentWillUnmount

Example: step by step

javascript
function Counter() { const [count, setCount] = useState(0); useEffect(() => { console.log('effect'); return () => console.log('cleanup'); }); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; }
  1. Mounting
  • React calls Counter() -> creates the Virtual DOM for <button>0</button>.
  • There is nothing to compare against yet -> creates the DOM element <button>.
  • After the commit it calls useEffect().
javascript
render -> diff -> commit -> useEffect
  1. Updating (setCount)
  • React calls Counter() again -> new Virtual DOM: <button>1</button>.
  • Compares the old (<button>0</button>) with the new (<button>1</button>) -> sees a difference in the text.
  • Changes only the text in the DOM.
  • Calls the cleanup of the old effect -> then the new useEffect.
javascript
render -> diff -> minimal DOM update -> cleanup -> new useEffect
  1. Unmounting
  • React removes the Counter Virtual DOM node.
  • Removes the <button> element from the DOM.
  • Calls cleanup() from useEffect.
javascript
unmount -> cleanup -> remove DOM

Relation to the Render and Commit phases

PhaseWork with the Virtual DOMLifecycle
Render phaseReact re-creates the Virtual DOM (in memory)the component's render(), reading state, computing JSX
Commit phaseReact updates the real DOM based on the diffuseLayoutEffect, useEffect, componentDidMount, componentDidUpdate, cleanup

Key point

The Virtual DOM is a "workspace" where React decides what needs to change. The lifecycle is a "timeline" of when those changes happen and when you can hook into that process.


Visually

javascript
(state changed) ┌──────────────┐ Render phase │ ← build a new Virtual DOM └──────────────┘ compare with the previous one (diffing) ┌──────────────┐ Commit phase │ ← update the real DOM └──────────────┘ call useEffect()

Would you like me to draw a diagram of a component's lifecycle (mounting -> updating -> unmounting) showing where React works with the Virtual DOM and where the hooks (useEffect, useLayoutEffect, cleanup) are called?

You said:

What causes a re-render?

ChatGPT said:

Great question - understanding exactly what causes a re-render is the key to optimizing React applications. Let's break it all down in detail, without "magic": when React decides to call a component again and why.


What is a "render" in general?

A "render" is a call of the component's function so React understands what JSX (that is, the Virtual DOM) it should display. This does not always mean a change to the real DOM!

React can call a component again, compare the result with the previous Virtual DOM, and... change nothing in the browser if everything is the same.


A re-render happens when React decides that it needs to recompute the JSX.

Here are all the reasons this happens


1. A change to state inside the component

The most obvious one:

javascript
function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; }
  • Every call to setCount() -> React calls Counter() again.
  • The component re-renders.
  • Then React compares the old and new Virtual DOM (diffing) -> and changes only the changed text in the real DOM.

Even if setState sets the same value (setCount(0) when count === 0), React compares the old and new value and does not trigger a re-render if they are equal (Object.is() comparison).


2. A change to props coming from the parent

If the parent re-rendered and passed new props (by reference or by value), React will re-render the child too.

javascript
function Child({ value }) { console.log('render child'); return <div>{value}</div>; } function Parent() { const [count, setCount] = useState(0); return ( <> <button onClick={() => setCount(c => c + 1)}>+</button> <Child value={count} /> {/* -> re-renders when count changes */} </> ); }
  • Parent changed -> React calls Parent().
  • The new Virtual DOM includes <Child value={newCount}>.
  • Child's prop changed -> it re-renders too.

Even if the parent changed an unused piece of state, the component function is still called, but React can optimize this with React.memo.


3. A change in context (useContext)

If you use context and its value has changed, all the components that read it through useContext() re-render:

javascript
const ThemeContext = createContext('light'); function App() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={theme}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar() { const theme = useContext(ThemeContext); // on change - re-render return <div>{theme}</div>; }

To keep all context consumers from re-rendering, use selectors or split the contexts.


4. A change in the parent

If the parent re-renders, then React by default re-renders all of its children, because it calls the parent's JSX again:

javascript
function Parent() { const [count, setCount] = useState(0); return ( <> <Child /> {/* will be called again */} <button onClick={() => setCount(c => c + 1)}>+</button> </> ); }

Even if Child does not depend on count, React will still recreate its JSX. To avoid this, use React.memo(Child).


5. A change to a component's key

If a component's key changes, React treats it as a new element -> unmounts the old one, mounts the new one.

javascript
{list.map(item => ( <Row key={item.id} data={item} /> ))}

If id changes -> Row is created anew (not updated).


6. A change to the parent's Context.Provider, even without a change in value

React calls all consumers again if a new value object is created on every render:

javascript
<ThemeContext.Provider value={{ color: 'black' }}>

Here a new object is created on every call -> useContext sees a new value -> a re-render. To avoid this, memoize the value:

javascript
<ThemeContext.Provider value={useMemo(() => ({ color: 'black' }), [])}>

7. An update through forceUpdate (rare, but possible)

In class components, this.forceUpdate() explicitly forces React to call render() again, even if state and props have not changed.


Key table

ReasonWhat changedDoes it cause a re-render?
setState() inside the componentYesYes
New props from the parentYesYes
The parent re-renderedEven without a change in propsYes
A context value (useContext)YesYes
A new object/function in props (by reference)Yes, if the reference changedYes
A new key on the elementYesYes (remounting)
setState() with the same valueNo (React compares with Object.is())No
React.memo() + identical propsNo changesNo

How to avoid unnecessary re-renders

  1. React.memo(Component) - memoization by props.
  2. useMemo() - memoization of computations inside the component.
  3. useCallback() - memoization of functions passed to children.
  4. useContextSelector() or splitting contexts.
  5. Do not create new objects/functions directly in JSX.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.