Debugging Tools and Techniques¶
Complete guide to debugging React and Next.js applications with modern tools and techniques.
React Developer Tools¶
Overview¶
Official Chrome/Firefox extension for React debugging.
Installation: - Chrome Extension - Firefox Extension
Key Features¶
Component Tree Inspection¶
- View component hierarchy
- Real-time props and state inspection
- Component name and location tracking
Profiler Tab¶
Records and analyzes component performance: 1. Click Record button 2. Perform actions in your app 3. Click Stop 4. Analyze render times, counts, and causes
What to look for: - Long render times (yellow/red bars) - Unnecessary re-renders - Component update triggers
Using the Profiler¶
// Wrap components to profile specific sections
import { Profiler } from 'react';
<Profiler id="MyComponent" onRender={callback}>
<MyComponent />
</Profiler>
Callback function:
function callback(
id, // "MyComponent"
phase, // "mount" or "update"
actualDuration, // Time spent rendering
baseDuration, // Estimated time without memoization
startTime, // When render started
commitTime, // When changes committed
interactions // Set of interactions
) {
console.log(`${id} took ${actualDuration}ms to render`);
}
why-did-you-render¶
Purpose¶
Detects unnecessary re-renders and identifies the cause.
Installation¶
Setup¶
For Next.js (_app.tsx or index.js):
if (process.env.NODE_ENV === "development") {
const React = require("react");
React.__DEV__ = true;
require("@welldone-software/why-did-you-render")(React, {
trackAllPureComponents: true,
});
}
Example Output¶
When unnecessary re-renders occur:
⚛️ [MyComponent] re-rendered because props.someValue changed
Previous: { value: 1 }
Current: { value: 1 }
→ Props appear equal but reference changed!
Configuration Options¶
require("@welldone-software/why-did-you-render")(React, {
trackAllPureComponents: true, // Track all pure components
trackHooks: true, // Track custom hooks
trackExtraHooks: [
[require('react-redux/lib'), 'useSelector']
],
logOnDifferentValues: true,
collapseGroups: true
});
React Render Tracker¶
Purpose¶
Visual highlighting of component renders.
Installation¶
Usage¶
Components flash/highlight when they re-render, making it easy to spot: - Over-rendering components - Cascading renders - Unexpected update triggers
Link: https://github.com/marko-js/react-render-tracker
ESLint + react-hooks Plugin¶
Purpose¶
Catch Hook mistakes before runtime.
Installation¶
Configuration¶
.eslintrc.json:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
Common Issues Detected¶
Missing dependencies:
// ❌ ESLint will warn
useEffect(() => {
console.log(userId);
}, []); // userId missing from deps
// ✅ Correct
useEffect(() => {
console.log(userId);
}, [userId]);
Conditional Hooks:
// ❌ Error - Hooks must be called unconditionally
if (condition) {
useEffect(() => {});
}
// ✅ Correct
useEffect(() => {
if (condition) {
// ...
}
}, [condition]);
Next.js Specific Debugging¶
Development Overlay¶
Next.js automatically displays: - Build errors - Runtime errors - Hydration mismatches - Server/Client component conflicts
Server Actions Debugging¶
"use server";
export async function myAction(formData: FormData) {
console.log("Server Action called"); // Logs in terminal, not browser
// Debug server-side logic here
}
Vercel DevTools (Experimental)¶
Provides: - Server Action traces - Edge Runtime monitoring - Request/response inspection
State Management Debugging¶
Zustand DevTools¶
import { mountStoreDevtool } from 'simple-zustand-devtools';
const useStore = create(...);
if (process.env.NODE_ENV === 'development') {
mountStoreDevtool('Store', useStore);
}
Jotai DevTools¶
import { useAtomsDebugValue } from 'jotai-devtools/utils';
function DebugAtoms() {
useAtomsDebugValue();
return null;
}
React Query DevTools¶
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
Console Debugging Techniques¶
Strategic Logging¶
// Component lifecycle
useEffect(() => {
console.log('Component mounted');
return () => console.log('Component unmounted');
}, []);
// Render count
const renderCount = useRef(0);
renderCount.current++;
console.log(`Render #${renderCount.current}`);
// Dependency tracking
useEffect(() => {
console.log('Dependencies changed:', { dep1, dep2 });
}, [dep1, dep2]);
Performance Marks¶
performance.mark('render-start');
// ... expensive operation
performance.mark('render-end');
performance.measure('render', 'render-start', 'render-end');
const measure = performance.getEntriesByName('render')[0];
console.log(`Render took ${measure.duration}ms`);
Common Issues and Solutions¶
Issue: Infinite useEffect Loop¶
Symptom: Component continuously re-renders
Debug:
Causes: - Object/array recreated every render - Missing dependencies - setState inside effect without conditions
Solution:
// ❌ Bad - object recreated every render
useEffect(() => {
fetchData({ userId });
}, [{ userId }]);
// ✅ Good - stable reference
useEffect(() => {
fetchData({ userId });
}, [userId]);
// ✅ Better - memoize complex objects
const config = useMemo(() => ({ userId }), [userId]);
useEffect(() => {
fetchData(config);
}, [config]);
Issue: Props Not Updating¶
Debug with React DevTools: 1. Select component in tree 2. Watch props panel while triggering update 3. Check if parent is re-rendering
Common causes: - Memoization preventing updates - Incorrect dependency arrays - State not lifting properly
Issue: Hydration Mismatch¶
Error message:
Debug:
// Check for server/client differences
const isClient = typeof window !== 'undefined';
console.log('Environment:', isClient ? 'client' : 'server');
// Use useEffect for client-only code
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return <LoadingState />;
return <ClientSpecificContent />;
Browser DevTools Integration¶
React Components Tab¶
Capabilities: - Search components by name - Filter by component type - Suspend component rendering - View source code location
Performance Tab¶
Record and analyze: - Component render times - Event handler performance - Network requests - Memory usage
Usage: 1. Open Chrome DevTools > Performance 2. Click Record 3. Perform actions 4. Stop and analyze flame graph
Debugging Best Practices¶
1. Systematic Approach¶
- Reproduce the issue consistently
- Isolate the problematic component
- Inspect props, state, and context
- Track re-render causes
- Verify fix doesn't break other features
2. Use TypeScript¶
// Type errors caught before runtime
interface Props {
userId: number; // Not string!
onUpdate: (id: number) => void;
}
function MyComponent({ userId, onUpdate }: Props) {
onUpdate(userId); // Type-safe
}
3. Enable Strict Mode¶
4. Environment-Specific Debugging¶
const DEBUG = process.env.NODE_ENV === 'development';
function debugLog(...args: any[]) {
if (DEBUG) console.log('[Debug]', ...args);
}
Quick Reference¶
| Tool | Purpose | When to Use |
|---|---|---|
| React DevTools | Inspect components | Always during development |
| why-did-you-render | Find unnecessary renders | Performance optimization |
| Profiler | Measure render performance | Before production release |
| ESLint Hooks Plugin | Catch Hook mistakes | In CI/CD pipeline |
| React Query DevTools | Debug API state | Data fetching issues |
| Zustand DevTools | Track global state | State management debugging |