React Performance: Avoiding Unnecessary Re-Renders
Unnecessary re-renders are the #1 cause of sluggish React apps. Learn how to identify and prevent them for smoother INP and better Lighthouse scores.
React's virtual DOM diffing is fast, but it's not free. When a parent component re-renders, all its children re-render too — even if their props haven't changed. On complex pages, this can cause hundreds of unnecessary re-renders per interaction, tanking INP scores.
Detecting Unnecessary Re-Renders
React DevTools Profiler
- Install React DevTools browser extension
- Open Profiler tab
- Click Record → interact with your app → Stop
- Look for components that re-render but show identical output
Highlight Updates
In React DevTools → Settings → "Highlight updates when components render". Components flash when they re-render. If everything flashes on a single state change, you have a problem.
Why-Did-You-Render Library
import whyDidYouRender from '@welldone-software/why-did-you-render';
if (process.env.NODE_ENV === 'development') {
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}
Top Re-Render Causes and Fixes
1. Inline Object/Array Props
// ❌ BAD: New object on every render → child re-renders
<UserList filters={{ status: 'active' }} />
// ✅ GOOD: Stable reference
const filters = useMemo(() => ({ status: 'active' }), []);
<UserList filters={filters} />
2. Inline Function Props
// ❌ BAD: New function on every render
<Button onClick={() => handleClick(id)} />
// ✅ GOOD: Memoized callback
const handleButtonClick = useCallback(() => handleClick(id), [id]);
<Button onClick={handleButtonClick} />
3. Context Overuse
When context value changes, ALL consumers re-render:
// ❌ BAD: Entire app re-renders on any state change
const AppContext = createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('dark');
const [cart, setCart] = useState([]);
return (
<AppContext.Provider value={{ user, theme, cart, setUser, setTheme, setCart }}>
<Everything /> {/* ALL children re-render on any change */}
</AppContext.Provider>
);
}
// ✅ GOOD: Split contexts by update frequency
const UserContext = createContext();
const ThemeContext = createContext();
const CartContext = createContext();
4. Missing React.memo
// ❌ BAD: Re-renders every time parent renders
function ExpensiveChart({ data }) {
// Complex rendering...
return <canvas />;
}
// ✅ GOOD: Only re-renders when data changes
const ExpensiveChart = React.memo(function ExpensiveChart({ data }) {
return <canvas />;
});
5. State Stored Too High
// ❌ BAD: Search input state in page → entire page re-renders on every keystroke
function Page() {
const [search, setSearch] = useState('');
return (
<>
<input value={search} onChange={e => setSearch(e.target.value)} />
<ExpensiveProductList /> {/* Re-renders on every keystroke */}
<Footer /> {/* Re-renders on every keystroke */}
</>
);
}
// ✅ GOOD: Isolate search state
function SearchInput({ onSearch }) {
const [search, setSearch] = useState('');
return <input value={search} onChange={e => setSearch(e.target.value)} />;
}
function Page() {
return (
<>
<SearchInput onSearch={handleSearch} />
<ExpensiveProductList />
<Footer />
</>
);
}
Performance Optimization Toolkit
| Problem | Solution |
|---|---|
| Expensive computation | useMemo() |
| Callback prop instability | useCallback() |
| Child re-renders without prop change | React.memo() |
| Too many consumers re-rendering | Split contexts |
| Large list rendering | Virtualization (react-window) |
| State too high in tree | Move state closer to where it's used |
Virtualization for Long Lists
Rendering 1000 items? Only render what's visible:
import { FixedSizeList } from 'react-window';
function ProductList({ items }) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={80}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<ProductRow product={items[index]} />
</div>
)}
</FixedSizeList>
);
}
This renders ~10 visible items instead of 1000, dramatically reducing DOM size and render time.
Monitor Your React App Performance
React performance issues often manifest as poor INP scores. BadPageSpeed tracks Core Web Vitals including interaction responsiveness.
Ready to stop wasting ad spend?
Track your landing page performance, monitor Core Web Vitals, and calculate exactly how much slow pages cost you.
Start Free — No Credit Card