ReactPerformanceMedium

When does React trigger a re-render?

01

The Short Answer

React re-renders a component when its state changes, when its parent re-renders (passing new props), or when a context value it consumes changes. A re-render means React calls your component function again, generates new JSX, diffs it against the previous output, and updates only the DOM nodes that actually changed.

02

The Three Triggers

1. State change

Calling a state setter (setState, dispatch) schedules a re-render of that component. React batches multiple state updates within the same event handler into a single re-render for efficiency.

state-trigger.tsxtypescript
function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    // These are batched — only ONE re-render happens
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  }

  // This function runs on every re-render
  console.log('Counter rendered with:', count);

  return <button onClick={handleClick}>{count}</button>;
}

2. Parent re-renders

When a parent component re-renders, all of its children re-render too — even if their props haven't changed. This is React's default behavior. The child's function runs again, produces new JSX, and React diffs it. If nothing changed in the output, no DOM updates happen, but the component function still executed.

parent-trigger.tsxtypescript
function Parent() {
  const [name, setName] = useState('Alice');

  // When name changes, Parent re-renders
  // → Child re-renders too (even though its props didn't change)
  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <Child /> {/* Re-renders on every keystroke */}
    </div>
  );
}

function Child() {
  console.log('Child rendered'); // Logs on every parent re-render
  return <p>I have no props but I still re-render</p>;
}

3. Context change

When a context value changes, every component that calls useContext for that context re-renders — regardless of whether it uses the specific part of the value that changed. This is why large context objects can cause performance issues.

context-trigger.tsxtypescript
const ThemeContext = createContext({ mode: 'light', accent: 'blue' });

function ThemedButton() {
  // Re-renders whenever ANY property in the context value changes
  // Even if this component only uses 'mode', changing 'accent' triggers re-render
  const { mode } = useContext(ThemeContext);
  console.log('ThemedButton rendered');
  return <button className={mode}>Click</button>;
}
03

What Does NOT Trigger a Re-render

It's equally important to know what doesn't cause re-renders. These are common misconceptions that lead to bugs when developers expect a re-render but don't get one.

These do NOT trigger re-renders

  • Mutating state directly — `state.count++` doesn't trigger anything; you must call the setter
  • Changing a ref value — `ref.current = newValue` never causes a re-render
  • Changing a variable defined outside state — regular `let` variables reset on re-render anyway
  • Props changing without parent re-rendering — props only change when the parent re-renders and passes new values

The code below demonstrates the most common mistake — mutating state directly. React has no way to know the object changed because the reference is the same. You must create a new object for React to detect the change.

mutation-mistake.tsxtypescript
function TodoList() {
  const [todos, setTodos] = useState([{ id: 1, text: 'Learn React' }]);

  function addTodo() {
    // ❌ Mutating — React doesn't re-render (same reference)
    todos.push({ id: 2, text: 'Build app' });
    setTodos(todos); // Same array reference — React bails out

    // ✅ New array — React detects the change and re-renders
    setTodos([...todos, { id: 2, text: 'Build app' }]);
  }
}
04

Preventing Unnecessary Re-renders

Re-renders are usually fast and harmless — React's diffing is efficient. But for expensive components or large lists, unnecessary re-renders can cause jank. Here are the tools React provides to skip them.

React.memo

React.memo wraps a component and skips re-rendering if its props haven't changed (shallow comparison). This breaks the "parent re-renders → child re-renders" default. But it only helps if the parent actually passes the same prop references — if you pass a new object or function on every render, memo is useless.

memo-example.tsxtypescript
// Only re-renders when 'name' or 'onClick' actually change
const ExpensiveChild = memo(function ExpensiveChild({
  name,
  onClick,
}: {
  name: string;
  onClick: () => void;
}) {
  console.log('ExpensiveChild rendered');
  return <button onClick={onClick}>{name}</button>;
});

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

  // ✅ Stable reference — memo can skip re-render
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {/* Won't re-render when count changes — props are stable */}
      <ExpensiveChild name="Alice" onClick={handleClick} />
    </div>
  );
}

useMemo and useCallback

useMemo memoizes a computed value, useCallback memoizes a function reference. Both prevent creating new references on every render, which is necessary for React.memo to work effectively on child components.

Don't optimize prematurely

Most re-renders are fast and don't need optimization. Only reach for memo/useMemo/useCallback when you've measured a performance problem. Adding memoization everywhere adds complexity and can actually be slower for simple components due to the comparison overhead.

05

The Re-render Lifecycle

Understanding what happens during a re-render helps you reason about performance and side effects.

1

Trigger

State change, parent re-render, or context change schedules a re-render.

2

Render phase

React calls your component function, which returns new JSX. This is pure computation — no DOM changes yet.

3

Reconciliation (diffing)

React compares the new JSX tree with the previous one to determine what actually changed.

4

Commit phase

React applies only the necessary DOM mutations. If nothing changed, no DOM updates happen.

5

Effects run

useEffect callbacks run after the DOM has been updated and painted to the screen.

06

Why Interviewers Ask This

Understanding re-render triggers is fundamental to writing performant React applications. Interviewers ask this to check whether you know the difference between a re-render and a DOM update, can identify what causes unnecessary re-renders, know when and how to optimize, and understand React's rendering model deeply enough to debug performance issues.

Quick Revision Cheat Sheet

State change: Calling setState/dispatch schedules a re-render of that component

Parent re-renders: Children re-render by default — even with unchanged props

Context change: All consumers re-render when the context value changes

React.memo: Skips re-render if props are shallowly equal

Re-render ≠ DOM update: React diffs the output — DOM only changes if the JSX actually differs