Why a Component Re-Renders
A component re-renders when its own state changes, when its parent re-renders (by default, all children re-render too), or when a context it consumes changes — most performance problems come from one of these three triggers firing more often than necessary.
React.memo
React.memo wraps a component so it skips re-rendering when its props haven’t changed (compared shallowly), even if its parent re-renders.
Skipping Re-Renders with React.memo
const ExpensiveRow = React.memo(function ExpensiveRow({ item }) {
console.log("Rendering row:", item.id);
return <li>{item.name}</li>;
});
// If the parent re-renders but 'item' didn't change,
// ExpensiveRow skips re-rendering entirely.React.memo Only Helps with Stable Props
If a prop is a new object, array, or function created fresh on every render, React.memo’s shallow comparison always sees it as "different," defeating the optimization. This is why React.memo is often paired with useMemo/useCallback on the parent’s side.
Code Splitting
Rather than shipping the entire app as one large JavaScript bundle, code splitting breaks it into smaller chunks that load on demand — reducing the amount of code the browser needs to download and parse before the first meaningful render.
Code Splitting a Route
import { lazy, Suspense } from "react";
const SettingsPage = lazy(() => import("./SettingsPage"));
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<SettingsPage />
</Suspense>
);
}
// SettingsPage's code is only downloaded when this component actually rendersVirtualizing Long Lists
Rendering thousands of DOM nodes at once (like a long table or feed) is expensive regardless of memoization. A virtualization library (like react-window or TanStack Virtual) renders only the items currently visible in the viewport, recycling DOM nodes as the user scrolls.
Common Optimization Techniques
| Technique | Solves |
|---|---|
| React.memo | A child re-rendering when its own props haven’t changed |
| useMemo / useCallback | Recreating expensive values/functions on every render |
| Code splitting (lazy + Suspense) | Shipping too much JavaScript up front |
| List virtualization | Rendering huge numbers of DOM nodes at once |
| Keying/structuring state well | Avoiding unnecessary re-renders from state changes higher than needed |
Measure Before You Optimize
The React DevTools Profiler shows exactly which components re-rendered, how long they took, and why. Optimizing without profiling first often targets the wrong component, or adds complexity for no measurable benefit.
Best Practice
Structure state so that changes only affect the smallest part of the tree that actually needs to update — many performance problems disappear once state lives at the right level, before any memoization is even needed.