skip to content

Unnecessary Re-Render Causes

A component re-renders because its own state changed, because its parent re-rendered, or because a context it consumes published a new value — not simply because "a prop changed". Reciting that list and mapping a slow screen onto it is the core of any React performance interview.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In React, what actually causes a component to re-render? Many candidates answer "its props changed" — explain why that answer is incomplete.

level: middleimportance: must knowfreq 78%

answer

  1. three triggers, not four
  2. props change because the parent rendered
  3. top-down by default, skipping is opt-in
  4. context consumers render on a new value
  5. render is not a DOM write

basics

~20 s

A React component re-renders when its own state changes, when its parent re-renders, or when a context it reads publishes a new value. Changed props are a consequence of the parent rendering, not a separate trigger.

solid answer

~50 s

There are three triggers. One: the component's own state changed, via a `useState` setter or a `useReducer` dispatch. Two: its parent re-rendered — React renders top-down, so rendering a parent means calling its children's functions too, whether or not any prop value differs. Three: a context the component reads was given a new value by its provider. "Props changed" is not a fourth trigger; the only way a child receives new props is that the parent rendered, so it is downstream of trigger two. The more useful half of the correction is the inverse: identical props do *not* stop a re-render, because React does not compare props before calling a child. Rendering is the default and skipping is the opt-in, via `React.memo` or by keeping the same element object. Also remember that render means calling the function and producing elements — the DOM is only touched where the diff finds a difference.

code

jsx · 16 lines
jsx
import { useState } from 'react';

function Child({ label }) {
  console.log('Child render');
  return <p>{label}</p>;
}

export default function Parent() {
  const [n, setN] = useState(0);
  return (
    <div>
      <button onClick={() => setN(n + 1)}>clicked {n}</button>
      <Child label="static" />
    </div>
  );
}

go deeper

for a junior

Be able to state the three triggers plainly — own state, parent rendered, context value changed — and to say that a child renders when its parent does, even with unchanged props.

for a middle

Explain the mechanism: rendering is top-down, React does not diff props before calling a child, and skipping is something you opt into. Also separate rendering from committing to the DOM.

for a senior

Show that you use the list as a diagnosis. Given a component rendering too often, name which trigger fired and what that implies about where state lives or how wide the provider's value is, before proposing any fix.

for a principal

Frame it as a design constraint: the shape of the tree and the placement of state determine the default render scope, so the cheapest long-term fix is usually structural rather than a memoization layer the team has to maintain.

## What "render" means before we ask what triggers it A render is React calling your component function and receiving back a tree of elements — plain JavaScript objects describing what the UI should look like. Rendering does not touch the DOM. Afterwards React compares the new element tree against the previous one and, in a separate commit step, applies only the differences to the real DOM. "My component re-rendered" and "the browser updated that part of the page" are therefore two different events. Conflating them is the single biggest reason candidates over-estimate what a re-render costs, and it is why the question is usually asked in a performance context. ## The three triggers **1. Its own state changed.** A `useState` setter or a `useReducer` dispatch schedules an update on that specific component. React re-renders it and, by default, the subtree beneath it. **2. Its parent re-rendered.** When React renders a component, it renders the elements that component returned, which means calling the child components, and their children, down the subtree. This is the default behaviour, and it happens whether or not any prop value changed — including for a child that takes no props at all. **3. A context it consumes published a new value.** A component calling `useContext(SomeContext)` re-renders when the nearest matching provider renders with a value that is not `Object.is`-equal to the previous one. This one is worth naming explicitly because it can reach a component whose parent chain bailed out. A fourth case is worth having ready but is not a re-render at all: if the element at a position gets a different `key`, React unmounts the old instance and mounts a fresh one, which discards its state — a heavier event than re-rendering. ## Why "props changed" is the wrong mental model Props are not a channel a parent can push down without rendering. New props exist only because the parent rendered and produced a new element for the child. So "props changed" never happens independently of trigger two. The inverse is what interviewers probe. Props being identical does not stop a re-render, because React does not diff props before calling a child component: ```jsx function Parent() { const [n, setN] = useState(0); return ( <> <button onClick={() => setN(n + 1)}>{n}</button> <Child label="static" /> </> ); } function Child({ label }) { console.log('Child render'); // logs on every click return <p>{label}</p>; } ``` `Child` receives the same string every time and produces the same output, yet its function runs on every click because `Parent` rendered. React still commits nothing for it — the diff finds no change — which is exactly why this render is usually harmless. ## What does not cause a re-render - **Mutating state in place.** Pushing into a state array and passing the same reference back gives React the same value; there is nothing new to render. - **Writing to a ref.** `ref.current = x` is untracked by design. - **Changing a module-level variable or a plain object the component happens to read.** React only knows about state, context, and subscriptions you registered. - **A store changing that you never subscribed to.** The subscription — `useSyncExternalStore`, or a store library's hook built on it — is what turns an external change into a render. - **An ancestor above a component that bailed out.** A bailout stops the cascade at that node, so everything below it is skipped too. ## Using the list in a performance conversation The practical value of the three triggers is that they turn "this screen is slow" into a diagnosis. For the component rendering too often, ask which trigger fired. Its own state? Then the state may be updated too often or modelled at the wrong granularity. Its parent? Then the state driving the update lives higher than the thing that actually changes, and the cascade is wide. Context? Then the provider's value changes more often, or carries more, than this consumer cares about. Each cause has a different fix, and reaching for memoization before naming the trigger is how teams end up with a memoized component that still re-renders on every keystroke.

  • If a child re-renders but React commits no DOM change, what did that render actually cost?
    Calling the component function, whatever work its body does, allocating the returned elements, and diffing them against the previous ones. For a small leaf that is microseconds. It becomes real cost when the subtree is wide, the body does heavy work like sorting thousands of rows, or the render repeats at input or animation rate.
  • Can a component re-render when neither its own state changed nor its parent rendered?
    Yes. A context it reads can publish a new value, which reaches the consumer directly even if the intervening components bailed out. A subscription to an external system via `useSyncExternalStore` does the same when the store emits. Both are per-component subscriptions rather than part of the top-down cascade.
  • What is the difference between a component re-rendering and a component remounting?
    Re-rendering calls the function again and keeps the instance, so state, refs and effects survive. Remounting destroys the instance — state is discarded, cleanup runs, effects re-run on the fresh one. React remounts when the element's type changes at that position, or when its `key` changes.

saying these in an interview costs you the question

  • Says a re-render is triggered by props changing
  • Thinks React compares props automatically before calling a child
  • Believes every re-render rewrites the DOM
  • Claims mutating a state array re-renders because the data changed
  • Assumes a child with no props can never re-render

context

open as a page

A React page holds a search box's value in state on the top-level page component and passes it down to a filtered table and a chart. Typing in the box is visibly laggy. What is the likely cause, and how would you confirm it before changing any code?

level: seniorimportance: must knowfreq 58%

basics

~20 s

The state lives too high, so every keystroke re-renders the whole page subtree including the chart and table. Confirm before fixing: profile a single keystroke and check whether the cost is many cheap renders or one expensive component's render body.

open as a page

While measuring render counts in a React application in development, you notice every component's function body runs twice for a single update. Should you treat that as a performance problem?

level: juniorimportance: should knowfreq 48%

basics

~10 s

No. React's StrictMode deliberately calls component functions twice in development to expose impure renders. Production builds render once, so measure a production build before drawing any performance conclusion from the doubling.

open as a page

In React, an extra re-render is not automatically a performance bug. How do you judge whether a particular re-render is costing anything, and which ones are worth eliminating?

level: seniorimportance: should knowfreq 42%

basics

~20 s

A re-render costs what it does: the size of the subtree it triggers, heavy computation in the render bodies, DOM changes the diff commits, and how often it repeats. Cheap renders at click rate are free; wide subtrees at keystroke rate are not.

open as a page