-
-
Notifications
You must be signed in to change notification settings - Fork 11
Cookbook Common Recipes
Step-by-step debugging workflows for typical React bugs. Each recipe names: the symptom, which tab to use, what to look for, and the fix.
Found a recipe missing? Add it here, or share it in Show & Tell and a maintainer will fold it in.
Symptom: Page feels slow, especially on interaction. Chrome's Performance tab shows yellow/red regions but doesn't say which React component is responsible.
Tab to use: Performance
Workflow:
- Open DevTools → React Debugger tab → Performance sub-tab
- Reproduce the lag (e.g., type in the search box, click a button)
- Look at "Top Re-rendering Components" — the top 3 are your suspects
- Click into a row → expand to see render reason (
props changed,state changed,context changed) - If "props changed" — open the Redux tab and check if a selector is returning a new reference on every dispatch
- If "state changed" — open the Timeline tab and find the
setStatecall
Fix patterns:
- For Redux: add
shallowEqualtouseSelector, or memoize the selector withcreateSelector - For local state: wrap the child component in
React.memoif the parent re-renders frequently - For context: split the context into smaller contexts that change less often
Symptom: Memory usage grows over time, especially on long-lived pages (dashboards, chat apps). Refreshing the page resets it; navigating doesn't.
Tab to use: Memory + Side Effects
Workflow:
- Open DevTools → React Debugger tab → Memory sub-tab
- Note the current heap size baseline
- Interact with the app for 60 seconds — do the things that should trigger the leak (open/close modals, switch tabs, etc.)
- Watch the heap-size sparkline:
- If it grows linearly and never recovers → likely a leak
- If it spikes and recovers → normal GC, not a leak
- Switch to Side Effects tab → look for "missing cleanup" warnings
- The leak is usually a
setInterval/ event listener / subscription that wasn't cleaned up
Fix pattern:
useEffect(() => {
const id = setInterval(handler, 1000);
return () => clearInterval(id); // ← this is what was missing
}, []);Symptom: Clicked a button, Redux DevTools shows the action fired, but the component doesn't re-render.
Tab to use: Redux + Performance
Workflow:
- Open Redux tab → confirm the action appears in the action history
- Expand the action → check the "state diff" — did the state actually change?
- If state DIDN'T change → your reducer returned the same reference. Check for mutation:
// ❌ MUTATION — same reference, no re-render case 'add': state.items.push(action.payload); return state; // ✅ NEW REFERENCE — re-render triggers case 'add': return { ...state, items: [...state.items, action.payload] };
- If state DID change → switch to Performance tab → check if the component re-rendered. If not, your
useSelectormay have a referential-equality issue (returning a new object slice each time, defeating React's bailout).
Symptom: Lighthouse reports a CLS score >0.1. You need to find which element is shifting.
Tab to use: CLS
Workflow:
- Open DevTools → React Debugger → CLS tab BEFORE loading the page (or refresh after opening)
- Watch the live CLS score climb
- Look at "Top Shift Contributors" — sorted by impact score
- Each contributor names the DOM element causing the shift
- Common culprits:
-
<img>without explicitwidth/height(image loads, page reflows) - Web fonts without
font-display: optional(FOIT/FOUT shifts) - Late-injecting ad/embed iframes
- Dynamic content (e.g., "Welcome back, Alice" replaces a skeleton)
-
Fix patterns:
- Always set
widthandheighton<img>(CSS can override but the attribute hints layout) - Reserve space for dynamic content with a skeleton of the same height
- Use
aspect-ratioCSS for responsive images - Defer non-critical embeds below the fold
Symptom: A console.log inside useEffect(() => {...}, []) appears twice on mount.
Tab to use: None — this is React's StrictMode doing its job
Workflow:
- Check
src/index.tsx(or equivalent) for<React.StrictMode>wrapping<App /> - If present: the double-mount is intentional in development to surface effect-cleanup bugs (React docs: "If your Effect breaks because of re-mounting, you need to implement a cleanup function.")
- In production builds, StrictMode is a no-op — the effect runs once
Fix pattern: Don't remove StrictMode. Instead, make your effect idempotent or add a cleanup that undoes the setup:
useEffect(() => {
const subscription = subscribeToFoo();
return () => subscription.unsubscribe();
}, []);If you DO need to suppress for a fixture or e2e test, omit StrictMode from that specific tree (e.g., test fixtures) — never from the main app.
Symptom: Performance tab shows a single component re-rendering dozens of times per second.
Tab to use: Performance + Timeline
Workflow:
- Performance tab → find the high-render component → note the count
- Click into it → look at "Render reason" for each render
- If reason is "state changed" → there's a
setStatein an effect with a missing dep - If reason is "props changed" → trace upward via the Owner Stack (planned for v2.3; for now use React DevTools' parent navigation)
- If reason is "context changed" → the context is updating too often; split it
Common cause: setState in a useEffect without deps, or with an unstable dep:
// ❌ Infinite loop
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Triggers re-render → effect runs again → setCount → ...
});
// ❌ Same problem, more subtle
useEffect(() => {
setData(processData(rawData));
}, [rawData, processData]); // processData is redeclared every render!
// ✅ Fix
const memoizedProcess = useCallback(processData, []);
useEffect(() => {
setData(memoizedProcess(rawData));
}, [rawData, memoizedProcess]);Symptom: AI Analysis tab flags a security issue, but you can't tell what file/line is affected.
Tab to use: AI Analysis + your editor
Workflow:
- AI Analysis tab → click into the flagged item
- Expand "Affected components" — these are the React component display names
- Search your codebase for those component names
- Read the AI's "Suggestion" field — usually names a specific pattern (e.g., "dangerouslySetInnerHTML with unsanitized user input", "fetching without await")
- If the suggestion is vague, regenerate the analysis after a fresh page load — the snapshot may have been incomplete
Honest caveat: AI Analysis can hallucinate. Treat it as a prompt for investigation, not gospel. If you can't reproduce the issue manually, it might be a false positive — and we'd love a bug report with the analysis JSON attached.
Got a debugging workflow that surfaced a non-obvious bug? Add it here:
- Click "Edit" on this wiki page (collaborator access required — comment in Discussions if you want collaborator access)
- Add your recipe at the bottom in the same format
- The maintainer will review and may reorder for prominence
Or, lower-friction route: share it in Show & Tell and we'll fold it in.
Wiki content edited by collaborators. For canonical reference, see the README and DEBUGGING-GUIDE. For bugs → Issues. For chat → Discussions.
Getting started
For users
For contributors
External