コンテンツにスキップ

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

npm install @welldone-software/why-did-you-render

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

npm install react-render-tracker

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

npm install eslint-plugin-react-hooks --save-dev

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:

useEffect(() => {
  console.log('Effect running', { dependency });
}, [dependency]);

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:

Warning: Text content did not match. Server: "X" Client: "Y"

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

  1. Reproduce the issue consistently
  2. Isolate the problematic component
  3. Inspect props, state, and context
  4. Track re-render causes
  5. 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

// Highlights potential problems
<React.StrictMode>
  <App />
</React.StrictMode>

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

Resources