Avoiding Unnecessary Re-renders
In this page:
Why Parent Re-renders Cascade to Children
By default, when a component re-renders, React also re-renders every child inside it, regardless of whether that child's own props actually changed. This is usually fine (React's diffing is fast), but for expensive children, it can become a real bottleneck if the parent re-renders very frequently.
Note: This cascading behavior is exactly what React.memo interrupts — wrapping a child in memo lets it opt out of re-rendering when its props haven't changed.
Warning: This default cascading behavior is not itself a bug — most re-renders are cheap; only address it once profiling shows an actual measured problem.
Example: Why Parent Re-renders Cascade to Children
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function Child() {
console.log("Child rendered");
return <p>I'm a child</p>;
}
function App() {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child /> {/* re-renders every time App does, even with no props */}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Keeping State Close to Where It's Used
Placing a piece of state as low in the component tree as possible (close to where it's actually read/used) limits how much of the app re-renders when it changes. Lifting state higher than necessary means every component between that higher point and the actual usage also re-renders on every change.
Note: Before lifting state up, ask if it genuinely needs to be shared, or if it could stay local to just the one component using it.
Warning: Storing rapidly-changing state (like mouse position or an input's live value) too high in the tree can cause large, unrelated sections to re-render on every update.
Example: Keeping State Close to Where It's Used
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// Better: input's state lives right where it's used, not lifted unnecessarily
function SearchBox() {
const [query, setQuery] = React.useState("");
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
function App() {
return <div><h1>App Title (never re-renders from typing)</h1><SearchBox /></div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Splitting Components to Isolate Re-renders
Breaking a large component into smaller, focused pieces means a state change only re-renders the specific piece that actually depends on it, rather than one giant component re-rendering entirely for any small internal change.
Note: This split naturally sets up good candidates for React.memo too, since each smaller piece now has its own clear, stable props.
Warning: Splitting components purely for performance reasons, without profiling data suggesting a real problem, can add structural complexity without measurable benefit.
Example: Splitting Components to Isolate Re-renders
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function Counter() {
const [count, setCount] = React.useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
function StaticHeader() {
console.log("StaticHeader rendered"); // only logs once, isolated from Counter's re-renders
return <h1>My App</h1>;
}
function App() {
return <div><StaticHeader /><Counter /></div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Storing state higher up the tree than necessary, causing large sections to re-render for a change only one small part actually needs.
- Passing the entire state object down as one prop when a component only uses one field, forcing re-renders on unrelated field changes.
- Not using the key prop correctly in lists, causing React to re-create (rather than update) items unnecessarily.
- A component re-renders when its own state changes, its parent re-renders, or its context value changes.
- Keeping state as close as possible to where it's used limits how much of the tree re-renders on a change.
- React.memo, useMemo, and useCallback are targeted tools for breaking specific unnecessary re-render chains.
- Splitting a large component into smaller ones can let React.memo prevent re-rendering the parts that didn't actually change.
No React-version restriction — these are general patterns; React.memo requires React 16.6+.
Chapter Quiz — Complete all 13 topics to unlock
0/13 topics done
Complete these topics first:
- Why Performance Matters
- Optimizing with React.memo
- Avoiding Unnecessary Re-renders
- Using the React Profiler
- Analyzing Bundle Size
- Image Optimization Techniques
- React as a PWA
- Creating a Production Build
- Environment Variables in React
- Deploying to Netlify
- Deploying to Vercel
- Basic CI/CD for React Apps
- SEO Basics for React SPAs