Identify and fix memory leaks in your React applications using Chrome DevTools and best practices.
Things You'll Need
- React application with suspected memory leak
- Chrome browser with DevTools
- Basic understanding of React hooks and lifecycle
Steps
Follow these 8 steps to complete this guide
Identify Memory Leak Symptoms
Watch for signs like gradually increasing memory usage, slow performance over time, or browser tab crashes. Open Chrome DevTools and monitor the Memory tab while using your app.
Tips
- Memory leaks are often noticed in long-running applications
- Look for memory usage that keeps increasing and never decreases
Take Heap Snapshots
Use Chrome DevTools to take heap snapshots at different points in your app's lifecycle. Compare snapshots to identify objects that aren't being garbage collected.
1. Open Chrome DevTools (F12)
2. Go to Memory tab
3. Select "Heap snapshot"
4. Click "Take snapshot"
5. Interact with your app
6. Take another snapshot
7. Compare snapshots to find retained objects Tips
- Take snapshots after performing the same action multiple times
- Look for objects with increasing counts between snapshots
Check for Cleanup in useEffect
The most common cause of memory leaks in React is forgetting to clean up subscriptions, timers, and event listeners in useEffect.
// BAD - Memory leak!
useEffect(() => {
const interval = setInterval(() => {
console.log('Running...');
}, 1000);
// Missing cleanup!
}, []);
// GOOD - Properly cleaned up
useEffect(() => {
const interval = setInterval(() => {
console.log('Running...');
}, 1000);
return () => {
clearInterval(interval); // Cleanup on unmount
};
}, []); Warnings
- Always return a cleanup function from useEffect when using subscriptions or timers
Fix Event Listener Leaks
Event listeners attached to window or document must be removed when the component unmounts.
// BAD
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
// GOOD
useEffect(() => {
const handleResize = () => {
// handle resize
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); Handle setState After Unmount
Calling setState on an unmounted component is a common source of memory leaks and warnings. Use cleanup flags to prevent this.
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
let isMounted = true;
fetch('/api/data')
.then(res => res.json())
.then(data => {
if (isMounted) {
setData(data);
}
});
return () => {
isMounted = false;
};
}, []);
return <div>{data}</div>;
} Tips
- Use AbortController for fetch requests to cancel them on unmount
- Consider using libraries like react-query that handle this automatically
Use the Performance Profiler
React DevTools Profiler can help identify components that re-render unnecessarily, which can contribute to memory issues.
1. Install React DevTools extension
2. Open DevTools > Profiler tab
3. Click record
4. Interact with your app
5. Stop recording
6. Analyze which components render and why Fix Closure Traps
Be careful with closures in useEffect and useCallback that capture old values or large objects.
// BAD - Captures entire data object
const handleClick = useCallback(() => {
console.log(data.value);
}, []); // Empty deps, but uses data
// GOOD - Properly specified dependencies
const handleClick = useCallback(() => {
console.log(data.value);
}, [data.value]); // Only captures what's needed Warnings
- Missing dependencies in useCallback/useMemo can cause stale closures
Test and Verify the Fix
After fixing potential leaks, take new heap snapshots and verify memory is being released properly.
Tips
- Mount and unmount the component multiple times
- Check if memory returns to baseline after unmounting
- Use the Memory tab's timeline view to visualize memory over time
Was this guide helpful?
Check out more step-by-step guides for development, deployment, debugging, and configuration.