An interactive React demo of a fact that feels like magic until you know it: a component passed as children (or any element prop) does not re-render when its parent re-renders — because the parent didn't create it.
▶ Live: https://children-rerender.pages.dev/
Bump each wrapper's state and watch the <Child> render counter. Same child, two ways of placing it — completely different behaviour.
Wrapper creates the child — the child re-renders every time the wrapper does:
function Wrapper() {
const [n, setN] = useState(0);
return (
<div onClick={() => setN(n + 1)}>
<Child /> // created HERE → new element on every Wrapper render
</div>
);
}Child passed as children — the child stays put:
function Wrapper({ children }) {
const [n, setN] = useState(0);
return (
<div onClick={() => setN(n + 1)}>
{children} // created by the PARENT → same element across re-renders
</div>
);
}
<Wrapper><Child /></Wrapper> // App makes <Child/>, onceIn the demo: bump the first wrapper and both counters climb together; bump the second and only the wrapper counter moves — the child holds at 1.
A component re-renders when either:
- its own state or props change, or
- its parent re-renders and hands it a freshly-created element.
children is created by the grandparent. When the wrapper re-renders, it hands back the same element reference it received — so React sees nothing changed for that subtree and bails out of re-rendering it. No memo involved; it's structural.
It's a free re-render boundary. If a component holds fast-changing state (a hover position, an input value, an animation frame) but wraps expensive content, take that content as children instead of rendering it inline. The state updates re-render the wrapper; the heavy children don't. This is exactly how <Context.Provider value={...}>{children}</Context.Provider> avoids re-rendering the whole tree, and it's the reasoning behind "lift expensive JSX up and pass it down."
Caveat: this only helps when the children genuinely don't depend on the wrapper's changing state. If they do, they should re-render — that's correctness, not waste.
React 19 · TypeScript · Vite. StrictMode is off so the render counts are real, not the dev-mode double-invoke.
npm install
npm run devMIT © 2026 dev48v — dev48v.infy.uk