In React, what actually causes a component's function to run again? Candidates often answer "when its props change" — say what really schedules a re-render and why props are not an independent trigger.
answer
- three triggers, not four
- props are pulled, never pushed
- own state, read context, parent ran
- identical props still re-run the child
- refs and module variables notify nobody
basics
~20 sReact re-runs a component when its own state changes, when a context it reads receives a new value, or when its parent re-renders. Props changing is not a separate trigger — it is a consequence of the parent running again.
solid answer
~50 sThere are three things that make React call a component function again. First, its own state changed — a setter from `useState` or `useReducer` was called. Second, a context it reads with `useContext` (or `use`) was given a new value by its provider. Third, its parent re-rendered, because rendering a parent produces the elements describing its children and React keeps rendering down into them. A fourth, rarer one is an explicit `root.render(...)` on the root created by `createRoot`. "Props changed" is not on the list: props are just arguments the parent computed while rendering, so a child can only see new props because the parent already re-ran. That is also why a child re-renders when the parent does even if every prop value is identical — skipping that is an explicit optimization, not the default.
code
javascript · 16 linesimport { useState } from 'react';
function Child({ label }) {
console.log('Child rendered with', label);
return <span>{label}</span>;
}
export default function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<Child label="never changes" />
</div>
);
}go deeper
Name the three triggers out loud — own state, a context you read, or the parent re-rendering — and say plainly that props changing is a result of the parent rendering, not a cause.
Be ready to explain why identical props still re-run a child, and to list what deliberately does not schedule a render: ref writes, mutations of state objects, and module-level variables.
Show that you use this to reason about real render storms: identify which component owns the state that fired, then which subtree that pulls in, before reaching for any optimization.
Own the architectural consequence — where state lives determines how much of the tree re-renders, so placing state near its consumers is a structural decision, not a micro-optimization applied after profiling.
## What "render" means here Rendering in React is React calling your component function and receiving a description of what the UI should look like — a tree of React elements. It is not a DOM update. A render can happen and result in zero DOM writes, so "my component re-rendered" and "the browser repainted that node" are different statements. Keeping those apart is most of what this question tests. ## The triggers **1. Its own state changed.** Calling the setter returned by `useState`, or the `dispatch` returned by `useReducer`, marks that component as needing work. React schedules a re-render of that component. (If the new value is the same as the current one React may skip the work — the details of that bail-out are the update-queue's business, not the trigger list's.) **2. A context it reads got a new value.** A component that calls `useContext(ThemeContext)` — or reads the context with `use(ThemeContext)` in React 19 — is subscribed to that context. When the nearest provider above it renders with a different `value`, every consumer of that context re-renders, even consumers that would otherwise have been skipped. **3. Its parent re-rendered.** This is the one people forget. When React re-runs a parent, the JSX the parent returns describes its children; React continues the render into those children by default. It does not compare the child's props first and decide whether the call is worth it. So a purely presentational child with constant props still runs again whenever its parent runs. **4. The root was re-rendered.** Calling `root.render(<App />)` again on the root returned by `createRoot` starts a render from the top. Applications rarely do this after startup. ## Why "props changed" is not a trigger Props are ordinary function arguments. The parent computes them while it is rendering and passes them into the child element it creates. Nothing pushes a prop into a mounted child from the outside; there is no channel for that. The only way a child can observe a different prop value is for the parent to have produced a new element with a new value — which means the parent rendered. So "props changed" is downstream of trigger 3, never a cause of its own. The practical consequence catches people out in both directions: - Same props, parent rendered → the child **does** re-render. - A value the child closes over changed outside React (a module variable, a mutable object) → the child does **not** re-render, because nothing told React. ## What does not trigger a render - Assigning to `ref.current`. Refs are deliberately outside the render trigger system; that is their purpose. - Mutating an object or array that is already in state, without calling the setter. React never sees it. - Mutating a module-level variable, a class instance, or `window`. - Changing a DOM node directly. - An external store changing, unless the component subscribed to it — that is what `useSyncExternalStore` exists for. ## Where the render starts and where it stops When a deeply nested component updates its own state, React starts the work at that component and walks down through the tree it returns. Ancestors and siblings are not re-run. So a `setState` in a leaf does not re-render the whole application — a very common misconception, usually voiced as "React re-renders everything on every state change". What it does re-render, by default, is that component's entire returned subtree. ```jsx function Page() { const [q, setQ] = useState(''); // typing here re-runs Page and everything Page returns, // including <Sidebar /> whose props never change return ( <> <input value={q} onChange={(e) => setQ(e.target.value)} /> <Sidebar /> <Results query={q} /> </> ); } ``` ## Why the default is affordable Re-running a component function is usually cheap: it allocates plain objects and returns them. React then compares that output with the previous one and only the actual differences are applied to the DOM in the commit that follows. The expensive parts of a web page — layout, paint, DOM mutation — are proportional to what changed, not to how many component functions ran. That is the trade React makes, and it is why the framework can afford to re-render a subtree instead of tracking which individual value each child depends on.
- If a parent re-renders and passes a child exactly the same prop values, does the child's function still run?Yes. React does not compare props before calling a child; re-rendering the returned subtree is the default. Skipping that work is an explicit opt-in optimization you apply deliberately, and it costs a comparison of its own, so the default of just re-running the function is usually the right one.
- Does assigning a new value to ref.current cause a re-render?No. Refs are a mutable box that lives across renders precisely because writing to them is invisible to React — nothing is scheduled. That is why refs suit values the UI does not display, like a timer id or a DOM node, and why a value the user must see belongs in state instead.
- A component deep in the tree calls its own setState. Which components re-render?That component and the tree it returns. React begins the work at the owner of the changed state and walks downward; its parent, its siblings, and everything above are untouched unless they are also part of that returned subtree. "React re-renders the whole app on every update" is simply wrong.
saying these in an interview costs you the question
- Says a component re-renders whenever its props change
- Believes React compares a child's props before calling it
- Claims every setState re-renders the entire application
- Thinks re-rendering means the DOM was updated
- Expects mutating a state object to trigger a render