When does React trigger a re-render?
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.
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.
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.
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.
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>;
}
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.
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' }]);
}
}
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.
// 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.
The Re-render Lifecycle
Understanding what happens during a re-render helps you reason about performance and side effects.
Trigger
State change, parent re-render, or context change schedules a re-render.
Render phase
React calls your component function, which returns new JSX. This is pure computation — no DOM changes yet.
Reconciliation (diffing)
React compares the new JSX tree with the previous one to determine what actually changed.
Commit phase
React applies only the necessary DOM mutations. If nothing changed, no DOM updates happen.
Effects run
useEffect callbacks run after the DOM has been updated and painted to the screen.
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