After optimizing React applications across fintech, automotive, and travel domains, I've identified the techniques that deliver the biggest performance wins. Here are 10 proven optimization strategies.
1. React.memo for Component Memoization
Wrap components that receive the same props frequently to prevent unnecessary re-renders.
const ExpensiveList = React.memo(({ items }: { items: Item[] }) => {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
});
2. useMemo for Expensive Computations
Cache the results of expensive calculations.
function Dashboard({ transactions }: Props) {
const totalRevenue = useMemo(
() => transactions.reduce((sum, t) => sum + t.amount, 0),
[transactions]
);
return <span>{totalRevenue}</span>;
}
3. useCallback for Stable References
Prevent child re-renders caused by new function references.
function ParentComponent() {
const handleClick = useCallback((id: string) => {
// handle click
}, []);
return ;
}
4. Code Splitting with React.lazy
Load components only when they're needed.
const HeavyChart = lazy(() => import('./HeavyChart'));
function Analytics() {
return (
Loading...</div>}>
);
}
5. Virtualize Long Lists
Render only visible items for large datasets.
import { FixedSizeList } from 'react-window';
function UserList({ users }: { users: User[] }) {
return (
{({ index, style }) => (
<div style={style}>{users[index].name}</div>
)}
);
}
6. Debounce User Input
Prevent excessive re-renders from rapid input changes.
- Delay network-bound or filtering work by roughly 150-300ms
- Pair debouncing with
AbortControllerfor fetch-heavy interactions - Avoid debouncing the visible input state itself
7. Optimize Context Usage
Split contexts to prevent unnecessary re-renders across the component tree.
- Keep auth, theme, permissions, and feature flags in separate contexts when practical
- Memoize provider values so consumers don't churn on every render
- Reach for selector patterns before introducing new state libraries
8. Use the key Prop Strategically
Force component remounting when data changes fundamentally.
- Reset a form when
userIdorrecordIdchanges - Remount charts when the data shape changes fundamentally
- Don't use keys to hide deeper state management bugs
9. Lazy Load Images
Use the native loading="lazy" attribute or Intersection Observer.
- Add
widthandheightoraspect-ratioto avoid layout shifts - Use eager loading and
fetchpriority="high"only for true hero media - Prefer responsive
srcsetover a single oversized asset
10. Profile with React DevTools
Always measure before optimizing. Use the React Profiler to identify actual bottlenecks.
- Capture the exact interaction that feels slow
- Compare flame charts before and after each change
- Re-test on lower-end hardware assumptions, not just your laptop
Key Takeaways
- Always measure performance before optimizing
- Focus on the techniques that address your specific bottlenecks
- React.memo and useMemo are your most-used tools
- Code splitting has the biggest impact on initial load time
- Virtualization is essential for large datasets
These techniques have helped me build applications processing millions of transactions with smooth, responsive UIs.
Originally published at umesh-malik.com
Top comments (0)