← Back to React Course | Chapter 14: Performance & Production Deployment | Lesson 3 of 13

Avoiding Unnecessary Re-renders

Avoiding unnecessary re-renders is like not repainting a whole room every time you move one chair — you only touch what actually changed.

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

markup
<!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

markup
<!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

markup
<!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>
Common Mistakes
  1. Storing state higher up the tree than necessary, causing large sections to re-render for a change only one small part actually needs.
  2. Passing the entire state object down as one prop when a component only uses one field, forcing re-renders on unrelated field changes.
  3. Not using the key prop correctly in lists, causing React to re-create (rather than update) items unnecessarily.
Chapter Summary
  • 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.
Browser Support

No React-version restriction — these are general patterns; React.memo requires React 16.6+.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.