A complete guide to debugging React applications using the React Debugger Chrome Extension. This guide covers all tabs, metrics, and debugging strategies for developers at every skill level.
- Getting Started
- Understanding the Tabs
- Debugging by Experience Level
- Common Issues and Fixes
- Best Practices
-
Build the extension:
npm install npm run build
-
Load in Chrome:
- Go to
chrome://extensions/ - Enable "Developer mode"
- Click "Load unpacked"
- Select the
distfolder
- Go to
-
Open DevTools (F12) and find the "React Debugger" tab
- Debugging slow or janky UI
- Finding memory leaks
- Tracking down unnecessary re-renders
- Identifying missing useEffect cleanups
- Monitoring layout shifts (CLS)
- Debugging Redux state
The Timeline tab provides a chronological view of all events happening in your React application.
| Icon | Type | Description |
|---|---|---|
| 🔁 | Render | Component render events |
| 📦 | State | State changes (local or Redux) |
| ⚡ | Effect | useEffect runs and cleanups |
| ❌ | Error | JavaScript errors and crashes |
| 🧠 | Memory | Memory snapshots and spikes |
- Render Order: Shows the sequence in which components rendered within a batch
- Trigger: What caused the render (props, state, context, parent, mount)
- Duration: How long the render took in milliseconds
- Batch ID: Groups related renders that happened together
- Filter Events: Click the filter buttons to show/hide event types
- Search: Use the search box to find specific components or actions
- Correlate: Click an event to see related events highlighted
- Snapshots: Capture render snapshots for later comparison
| Observation | Potential Issue |
|---|---|
| Same component rendering many times | Unnecessary re-renders |
| Long render durations (>16ms) | Slow component - needs optimization |
| Rapid state changes | Possible infinite loop |
| Effects running without cleanup | Memory leak risk |
Detects common React anti-patterns related to UI rendering and state management.
| Issue | Severity | Description |
|---|---|---|
| DIRECT_STATE_MUTATION | Error | Modifying state directly instead of using setState |
| MISSING_KEY | Warning | List items without key prop |
| INDEX_AS_KEY | Warning | Using array index as key (problematic for dynamic lists) |
| DUPLICATE_KEY | Error | Multiple items with the same key |
// BAD - Direct mutation
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // Mutating state directly!
setItems(items);
// GOOD - Create new array
setItems([...items, 4]);// BAD - No keys
{items.map(item => <li>{item}</li>)}
// GOOD - Unique keys
{items.map(item => <li key={item.id}>{item.name}</li>)}// BAD - Index as key (problematic when list changes)
{items.map((item, index) => <li key={index}>{item}</li>)}
// GOOD - Stable unique ID
{items.map(item => <li key={item.id}>{item}</li>)}Comprehensive performance analysis including render statistics and Core Web Vitals.
| Metric | Description | Good Value |
|---|---|---|
| Components | Total tracked components | N/A |
| Total Renders | Sum of all component renders | Lower is better |
| Avg Render Time | Average render duration | < 16ms |
| Slow Renders | Renders exceeding 16ms | 0 |
| Metric | Full Name | Good | Needs Improvement | Poor |
|---|---|---|---|---|
| FCP | First Contentful Paint | < 1.8s | 1.8s - 3s | > 3s |
| LCP | Largest Contentful Paint | < 2.5s | 2.5s - 4s | > 4s |
| TTFB | Time to First Byte | < 0.8s | 0.8s - 1.8s | > 1.8s |
Enable "React Scan" to visualize re-renders directly on the page:
| Color | Render Count |
|---|---|
| Green | 1 render |
| Yellow | 2-3 renders |
| Orange | 4-5 renders |
| Red-Orange | 6-10 renders |
| Red | 10+ renders |
Slowest Components Table
- Component: Name of the React component
- Max Time: Longest render duration recorded
- Avg Time: Average render time across all renders
- Renders: Total number of times rendered
Top Re-rendering Components Table
- Component: Name of the React component
- Renders: Total render count
- Avg Time: Average total render time
- Self Time: Time spent in the component itself (excluding children)
- Last Trigger: What caused the most recent render
| Trigger | Meaning | Common Fix |
|---|---|---|
| props | Props changed | Use React.memo, optimize parent |
| state | Local state changed | Reduce state updates |
| context | Context value changed | Split contexts, memoize values |
| parent | Parent re-rendered | Use React.memo |
| mount | Initial mount | Normal, no fix needed |
Monitor JavaScript heap usage and detect memory leaks.
| Metric | Description |
|---|---|
| Used Heap | Currently allocated memory |
| Total Heap | Total memory available to JS |
| Heap Limit | Maximum memory limit |
| Peak Usage | Highest memory usage recorded |
| Growth Rate | Memory change per second |
| Usage % | Status | Action |
|---|---|---|
| < 70% | Healthy | No action needed |
| 70-90% | Warning | Monitor closely |
| > 90% | Critical | Investigate immediately |
| Rate | Status | Meaning |
|---|---|---|
| Negative | Good | Memory being freed (GC) |
| 0 - 512KB/s | Normal | Typical fluctuation |
| 512KB - 1MB/s | Warning | Possible leak |
| > 1MB/s | Critical | Likely memory leak |
The Memory tab also captures:
- JavaScript errors
- Unhandled promise rejections
- React error boundary catches
Each crash includes:
- Timestamp
- Error message
- Stack trace
- Memory state at crash time
- Analysis hints
Analyze useEffect hooks for common issues.
Effects that set up subscriptions, timers, or listeners but don't clean up.
// BAD - No cleanup
useEffect(() => {
const id = setInterval(() => console.log('tick'), 1000);
// Missing: return () => clearInterval(id);
}, []);
// GOOD - With cleanup
useEffect(() => {
const id = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(id);
}, []);Missing Dependency
// BAD - count not in deps
useEffect(() => {
console.log(count);
}, []); // Should include [count]Extra Dependency
// BAD - unnecessary dependency
useEffect(() => {
fetchData();
}, [somethingUnrelated]); // Causes unnecessary re-runs// BAD - Setting state that triggers re-render that re-runs effect
useEffect(() => {
setCount(count + 1); // Infinite loop!
}, [count]);// BAD - Callback captures stale value
useEffect(() => {
const handleClick = () => {
console.log(count); // Always logs initial value
};
window.addEventListener('click', handleClick);
return () => window.removeEventListener('click', handleClick);
}, []); // Missing count dependencyMonitor Cumulative Layout Shift - a Core Web Vital measuring visual stability.
| Score | Rating | User Experience |
|---|---|---|
| < 0.1 | Good | Stable, no jarring shifts |
| 0.1 - 0.25 | Needs Improvement | Noticeable shifts |
| > 0.25 | Poor | Significant layout instability |
| Column | Description |
|---|---|
| Element | CSS selector of shifting element |
| Total Shift | Cumulative shift score from this element |
| Occurrences | How many times this element shifted |
| Cause | Fix |
|---|---|
| Images without dimensions | Add width and height attributes |
| Dynamic content insertion | Reserve space with min-height |
| Font loading | Use font-display: swap |
| Ads/embeds | Set explicit dimensions |
| Animations | Use transform instead of top/left |
Debug Redux state and dispatch actions.
-
State Tree Browser
- Expandable/collapsible tree view
- Search functionality
- Direct value editing
- Array item manipulation (move, delete)
-
Action History
- Chronological list of dispatched actions
- Click to view action details and payload
-
Action Dispatcher
- Send custom actions for testing
- JSON payload support
The extension detects Redux via:
window.storewindow.__REDUX_STORE__- Redux DevTools Extension
- React-Redux Provider context
// Expose store for debugging
if (process.env.NODE_ENV === 'development') {
window.store = store;
}Focus Areas:
- UI & State Tab - Learn proper React patterns
- Side Effects Tab - Understand useEffect
Workflow:
- Open the extension after your app loads
- Check UI & State tab for red/yellow issues
- Read the suggestions and fix one at a time
- Check Side Effects tab for missing cleanups
Key Things to Remember:
- Always use unique keys in lists (not array index)
- Never mutate state directly
- Always clean up timers and event listeners
Focus Areas:
- Performance Tab - Optimize render performance
- Timeline Tab - Understand render cascades
- Memory Tab - Prevent memory leaks
Workflow:
- Enable React Scan to visualize re-renders
- Identify components rendering excessively
- Use Timeline to trace render triggers
- Apply React.memo, useMemo, useCallback
Optimization Checklist:
- Components with >10 renders - investigate
- Render times >16ms - optimize or split
- Parent renders causing child renders - add memoization
- Context changes causing wide re-renders - split contexts
Focus Areas:
- All tabs - holistic view
- Timeline correlations - find root causes
- Memory patterns - detect slow leaks
- CLS optimization - improve UX metrics
Advanced Workflow:
- Take Timeline snapshots at key user interactions
- Compare render patterns before/after changes
- Monitor memory over extended sessions
- Correlate Redux actions with render cascades
Architecture Considerations:
- Component boundaries for render isolation
- State colocation vs lifting
- Context granularity
- Lazy loading strategies
Symptoms:
- High render count in Performance tab
- Excessive re-renders visible with React Scan
- Slow/janky UI
Debugging Steps:
- Find component in "Top Re-rendering Components"
- Check "Last Trigger" column
- If trigger is "parent" - parent needs optimization
- If trigger is "props" - props are changing unnecessarily
- If trigger is "context" - context is too broad
Fixes:
// Memoize component
export const MyComponent = React.memo(({ data }) => {
return <div>{data.name}</div>;
});
// Memoize expensive calculations
const processed = useMemo(() => expensiveCalculation(data), [data]);
// Stable callback references
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);Symptoms:
- Memory growth rate consistently positive
- "Possible memory leak" warning
- App becomes slow over time
Debugging Steps:
- Start memory monitoring
- Perform repetitive actions (navigate, open/close modals)
- Check if memory returns to baseline
- Look for missing cleanups in Side Effects tab
Common Fixes:
// Clean up subscriptions
useEffect(() => {
const subscription = api.subscribe(callback);
return () => subscription.unsubscribe();
}, []);
// Clean up timers
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
// Clean up event listeners
useEffect(() => {
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);Symptoms:
- CLS score > 0.1
- Elements jumping around on page load
- Poor user experience
Debugging Steps:
- Check CLS tab for top contributors
- Note which elements are shifting
- Inspect those elements for missing dimensions
Fixes:
// Images - always set dimensions
<img src="photo.jpg" width={300} height={200} alt="Photo" />
// Or use aspect-ratio
<img src="photo.jpg" style={{ aspectRatio: '16/9', width: '100%' }} />
// Dynamic content - reserve space
<div style={{ minHeight: 200 }}>
{loading ? <Skeleton /> : <Content />}
</div>Symptoms:
- Callback uses outdated state values
- setTimeout/setInterval logs old values
- Event handlers have wrong data
Debugging Steps:
- Check Side Effects tab for "Stale Closures"
- Review the captured vs current values
- Add missing dependencies or use refs
Fixes:
// Option 1: Add dependency
useEffect(() => {
const handler = () => console.log(count);
window.addEventListener('click', handler);
return () => window.removeEventListener('click', handler);
}, [count]); // Re-subscribe when count changes
// Option 2: Use ref for latest value
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
const handler = () => console.log(countRef.current);
window.addEventListener('click', handler);
return () => window.removeEventListener('click', handler);
}, []); // No need to re-subscribe- Measure before optimizing - Use the Performance tab to identify actual bottlenecks
- Start with the slowest components - Fix the biggest issues first
- Memoize strategically - Not everything needs React.memo
- Split large components - Smaller components = better caching
- Virtualize long lists - Use react-window or react-virtual
- Always clean up effects - Return cleanup functions
- Monitor over time - Memory leaks are gradual
- Test navigation patterns - Navigate back and forth repeatedly
- Check event listeners - Common source of leaks
- Be careful with closures - They capture references
- Use stable keys - Never use array index for dynamic lists
- Never mutate state - Always create new objects/arrays
- Colocate state - Keep state close to where it's used
- Split contexts - Avoid one giant context
- Set image dimensions - Always include width/height
- Reserve space - Use min-height for dynamic content
- Avoid top/left animations - Use transform instead
- Preload fonts - Prevent FOUT causing shifts
| Action | Key |
|---|---|
| Filter render events | Click filter button |
| Search events | Type in search box |
| Expand all (Redux) | Click + button |
| Collapse all (Redux) | Click - button |
| Color | Meaning |
|---|---|
| Green | Good / Healthy |
| Yellow | Warning / Needs attention |
| Red | Error / Critical |
| Blue | Informational |
| Metric | Good | Warning | Poor |
|---|---|---|---|
| Render time | < 16ms | 16-50ms | > 50ms |
| Memory usage | < 70% | 70-90% | > 90% |
| CLS score | < 0.1 | 0.1-0.25 | > 0.25 |
| FCP | < 1.8s | 1.8-3s | > 3s |
| LCP | < 2.5s | 2.5-4s | > 4s |
If you encounter issues not covered in this guide:
- Check the console for additional error messages
- Review the Timeline tab for correlation between events
- Take snapshots and compare before/after states
- File an issue on the project repository
React Debugger Extension - Making React debugging easier for everyone