State Debugging and Development Tools

1. React DevTools for State Inspection

React DevTools provides powerful state inspection, component hierarchy visualization, and performance profiling capabilities.

DevTools Feature Description Use Case
Components Tab Inspect component tree and props/state View current component state values
State Editing Modify state values in real-time Test component behavior with different states
Hooks Inspection View all hooks and their values Debug useState, useReducer, useContext
Profiler Tab Record and analyze render performance Identify unnecessary re-renders
Component Highlighting Visual feedback for component updates See which components re-render
Search & Filter Find components by name or type Navigate large component trees

Example: Inspecting Component State with DevTools

// TodoApp.jsx
import { useState, useReducer } from 'react';

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, { id: Date.now(), text: action.text, done: false }];
    case 'TOGGLE':
      return state.map(t => 
        t.id === action.id ? { ...t, done: !t.done } : t
      );
    default:
      return state;
  }
}

export function TodoApp() {
  const [input, setInput] = useState('');
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [filter, setFilter] = useState('all');

  // In React DevTools Components tab, you can:
  // 1. Select TodoApp component
  // 2. View hooks section showing:
  //    - State: input (string)
  //    - Reducer: todos (array)
  //    - State: filter (string)
  // 3. Edit values directly to test different scenarios
  // 4. See props passed to child components

  return (
    <div>
      <input 
        value={input} 
        onChange={(e) => setInput(e.target.value)}
        placeholder="Add todo"
      />
      <button onClick={() => {
        dispatch({ type: 'ADD', text: input });
        setInput('');
      }}>Add</button>
      
      <select value={filter} onChange={(e) => setFilter(e.target.value)}>
        <option value="all">All</option>
        <option value="active">Active</option>
        <option value="completed">Completed</option>
      </select>

      <ul>
        {todos
          .filter(t => {
            if (filter === 'active') return !t.done;
            if (filter === 'completed') return t.done;
            return true;
          })
          .map(todo => (
            <li key={todo.id}>
              <input
                type="checkbox"
                checked={todo.done}
                onChange={() => dispatch({ type: 'TOGGLE', id: todo.id })}
              />
              {todo.text}
            </li>
          ))}
      </ul>
    </div>
  );
}

Example: Using DevTools Component Displayname for Better Debugging

// Add display names to components for better DevTools experience
import { useState, memo } from 'react';

// Functional component with display name
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  // ... state logic
  return <div>{user?.name}</div>;
}
UserProfile.displayName = 'UserProfile';

// Memoized component with display name
const MemoizedItem = memo(function Item({ item }) {
  return <div>{item.name}</div>;
});
MemoizedItem.displayName = 'MemoizedItem';

// Custom hook - shows in DevTools hooks section
function useDebugValue(value, formatFn) {
  // useDebugValue shows custom label in DevTools
  useDebugValue(value, v => `User: ${v?.name || 'None'}`);
  return value;
}

// Higher-order component with proper naming
function withLoading(Component) {
  function WithLoading(props) {
    const [loading, setLoading] = useState(true);
    // ... loading logic
    return loading ? <div>Loading...</div> : <Component {...props} />;
  }
  
  WithLoading.displayName = `WithLoading(${Component.displayName || Component.name})`;
  return WithLoading;
}

// Usage in DevTools:
// - Components show meaningful names instead of "Anonymous"
// - Custom hooks display formatted debug values
// - HOCs show wrapped component name for easier debugging
Note: Enable "Highlight updates when components render" in DevTools settings to visually see which components re-render on state changes. This helps identify performance issues.

2. Redux DevTools Extension Integration

Redux DevTools Extension provides time-travel debugging, action history, and state diff visualization for Redux applications.

Feature Description Use Case
Action History View all dispatched actions chronologically Track state changes over time
State Diff See state changes for each action Understand action impact
Time Travel Jump to any previous state Debug specific state scenarios
Action Replay Replay actions from history Reproduce bugs
State Export/Import Save and load application state Share bug reproductions
Action Filtering Show/hide specific action types Focus on relevant actions

Example: Redux Toolkit with DevTools Integration

// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import userReducer from './userSlice';

// Redux Toolkit automatically enables Redux DevTools in development
export const store = configureStore({
  reducer: {
    counter: counterReducer,
    user: userReducer,
  },
  // DevTools configuration (optional)
  devTools: process.env.NODE_ENV !== 'production' ? {
    name: 'My App',
    trace: true, // Enable action stack traces
    traceLimit: 25,
    features: {
      pause: true, // Pause recording
      lock: true, // Lock/unlock state changes
      persist: true, // Persist state in localStorage
      export: true, // Export state
      import: 'custom', // Import state
      jump: true, // Jump to action
      skip: true, // Skip actions
      reorder: true, // Reorder actions
      dispatch: true, // Dispatch custom actions
      test: true // Generate tests
    }
  } : false
});

// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0, history: [] },
  reducers: {
    increment: (state) => {
      state.value += 1;
      // DevTools will show:
      // Action: counter/increment
      // Diff: +value: 1
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
      // DevTools shows payload value in action details
    },
    // Add action metadata for better DevTools display
    reset: {
      reducer: (state) => {
        state.value = 0;
        state.history = [];
      },
      prepare: () => ({
        payload: undefined,
        meta: { timestamp: Date.now(), reason: 'user_reset' }
      })
    }
  }
});

export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
export default counterSlice.reducer;

Example: Custom Redux DevTools Integration for Non-Redux State

// Custom hook with Redux DevTools integration
import { useReducer, useEffect, useRef } from 'react';

function useReducerWithDevTools(reducer, initialState, name = 'State') {
  const [state, dispatch] = useReducer(reducer, initialState);
  const devTools = useRef(null);

  useEffect(() => {
    // Connect to Redux DevTools Extension
    if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
      devTools.current = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
        name,
        features: { jump: true, skip: true }
      });

      // Send initial state
      devTools.current.init(initialState);
    }

    return () => {
      if (devTools.current) {
        devTools.current.unsubscribe();
      }
    };
  }, [name, initialState]);

  // Wrapper dispatch that notifies DevTools
  const dispatchWithDevTools = (action) => {
    dispatch(action);
    
    if (devTools.current) {
      // Send action to DevTools
      devTools.current.send(action, reducer(state, action));
    }
  };

  // Subscribe to DevTools messages (time travel)
  useEffect(() => {
    if (!devTools.current) return;

    const unsubscribe = devTools.current.subscribe((message) => {
      if (message.type === 'DISPATCH' && message.state) {
        // Handle time-travel: update state from DevTools
        const newState = JSON.parse(message.state);
        // Note: This requires modifying the reducer to accept a SET_STATE action
        dispatch({ type: '@@DEVTOOLS/SET_STATE', payload: newState });
      }
    });

    return unsubscribe;
  }, []);

  return [state, dispatchWithDevTools];
}

// Usage
function TodoApp() {
  const [state, dispatch] = useReducerWithDevTools(
    todoReducer,
    { todos: [], filter: 'all' },
    'TodoApp'
  );

  // Now your useReducer state appears in Redux DevTools!
  // You can time-travel, see action history, export state, etc.

  return (
    <div>
      <button onClick={() => dispatch({ type: 'ADD_TODO', text: 'Learn DevTools' })}>
        Add Todo
      </button>
      {/* ... */}
    </div>
  );
}
Note: Redux DevTools can integrate with any state management solution, not just Redux. Use the __REDUX_DEVTOOLS_EXTENSION__ API to connect custom state managers.

3. State Logging and Debug Patterns

Implement structured logging patterns to track state changes and debug issues in development and production.

Logging Pattern Description Use Case
Console logging Log state changes to browser console Development debugging
Middleware logging Redux middleware for action logging Track all state mutations
State snapshots Capture state at specific points Compare state before/after
Conditional logging Log only in specific conditions Filter noise, focus on issues
Performance logging Log render times and counts Identify performance bottlenecks

Example: Custom useDebugState Hook with Logging

// useDebugState.js
import { useState, useEffect, useRef } from 'react';

export function useDebugState(initialValue, name = 'State') {
  const [state, setState] = useState(initialValue);
  const previousState = useRef(initialValue);
  const renderCount = useRef(0);

  useEffect(() => {
    renderCount.current += 1;
  });

  const setStateWithDebug = (newValue) => {
    const valueToSet = typeof newValue === 'function' 
      ? newValue(state) 
      : newValue;

    console.group(`šŸ”µ ${name} State Update`);
    console.log('Previous:', previousState.current);
    console.log('New:', valueToSet);
    console.log('Render Count:', renderCount.current);
    console.log('Timestamp:', new Date().toISOString());
    console.trace('Update triggered from:');
    console.groupEnd();

    previousState.current = state;
    setState(valueToSet);
  };

  // Log on every render in development
  if (process.env.NODE_ENV === 'development') {
    console.log(`šŸ”„ ${name} rendered:`, state, `(${renderCount.current} times)`);
  }

  return [state, setStateWithDebug];
}

// Usage
function Counter() {
  const [count, setCount] = useDebugState(0, 'Counter');
  const [step, setStep] = useDebugState(1, 'Step');

  return (
    <div>
      <p>Count: {count}</p>
      <p>Step: {step}</p>
      <button onClick={() => setCount(c => c + step)}>
        Increment by {step}
      </button>
      <button onClick={() => setStep(s => s + 1)}>
        Increase Step
      </button>
    </div>
  );
  
  // Console output when clicking Increment:
  // šŸ”„ Counter rendered: 0 (1 times)
  // šŸ”„ Step rendered: 1 (1 times)
  // šŸ”µ Counter State Update
  //   Previous: 0
  //   New: 1
  //   Render Count: 1
  //   Timestamp: 2025-12-20T10:30:45.123Z
  //   Update triggered from: [stack trace]
}

Example: Redux Logger Middleware

// Custom logger middleware
const loggerMiddleware = (store) => (next) => (action) => {
  const prevState = store.getState();
  const startTime = performance.now();

  console.group(`⚔ Action: ${action.type}`);
  console.log('Payload:', action.payload);
  console.log('Previous State:', prevState);
  
  // Call the next middleware or reducer
  const result = next(action);
  
  const nextState = store.getState();
  const endTime = performance.now();
  
  console.log('Next State:', nextState);
  console.log('State Diff:', getStateDiff(prevState, nextState));
  console.log(`ā±ļø Time: ${(endTime - startTime).toFixed(2)}ms`);
  console.groupEnd();
  
  return result;
};

// Helper to show state differences
function getStateDiff(prev, next) {
  const diff = {};
  Object.keys(next).forEach(key => {
    if (prev[key] !== next[key]) {
      diff[key] = { from: prev[key], to: next[key] };
    }
  });
  return diff;
}

// Redux Toolkit setup with logger
import { configureStore } from '@reduxjs/toolkit';
import logger from 'redux-logger'; // Or use custom logger above

export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(
      process.env.NODE_ENV === 'development' ? logger : []
    )
});

// Conditional logging based on action type
const selectiveLogger = (store) => (next) => (action) => {
  // Only log specific actions
  const actionsToLog = ['user/login', 'cart/addItem', 'order/submit'];
  
  if (actionsToLog.some(type => action.type.includes(type))) {
    console.log('šŸŽÆ Important Action:', action);
    console.log('Current State:', store.getState());
  }
  
  return next(action);
};

Example: Production-Safe State Logging

// logger.js - Production-safe logging utility
class StateLogger {
  constructor(options = {}) {
    this.enabled = options.enabled ?? process.env.NODE_ENV === 'development';
    this.maxLogs = options.maxLogs ?? 100;
    this.logs = [];
    this.excludeActions = options.excludeActions ?? [];
  }

  log(type, data) {
    if (!this.enabled) return;

    const logEntry = {
      type,
      data,
      timestamp: Date.now(),
      url: window.location.href
    };

    this.logs.push(logEntry);

    // Keep only recent logs
    if (this.logs.length > this.maxLogs) {
      this.logs.shift();
    }

    // Console output in development only
    if (process.env.NODE_ENV === 'development') {
      console.log(`[${type}]`, data);
    }
  }

  logStateChange(actionType, prevState, nextState) {
    if (this.excludeActions.includes(actionType)) return;

    this.log('STATE_CHANGE', {
      action: actionType,
      before: this.sanitizeState(prevState),
      after: this.sanitizeState(nextState)
    });
  }

  // Remove sensitive data before logging
  sanitizeState(state) {
    const sanitized = { ...state };
    const sensitiveKeys = ['password', 'token', 'creditCard', 'ssn'];
    
    Object.keys(sanitized).forEach(key => {
      if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
        sanitized[key] = '[REDACTED]';
      }
    });
    
    return sanitized;
  }

  // Export logs for bug reports
  exportLogs() {
    return JSON.stringify(this.logs, null, 2);
  }

  // Download logs as file
  downloadLogs() {
    const blob = new Blob([this.exportLogs()], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `state-logs-${Date.now()}.json`;
    a.click();
    URL.revokeObjectURL(url);
  }

  // Send logs to error tracking service
  async sendToErrorTracking(error) {
    if (process.env.NODE_ENV === 'production') {
      // Send to Sentry, LogRocket, etc.
      await fetch('/api/error-logs', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          error: error.message,
          stack: error.stack,
          stateLogs: this.logs
        })
      });
    }
  }
}

export const stateLogger = new StateLogger({
  enabled: true,
  excludeActions: ['@@INIT', 'persist/REHYDRATE']
});
Warning: Never log sensitive data (passwords, tokens, PII) in production. Always sanitize state before logging and disable verbose logging in production builds.

4. Time-travel Debugging for State Changes

Implement time-travel debugging to replay state changes and jump to any previous application state.

Technique Description Use Case
State history stack Store all state snapshots Navigate through past states
Action replay Re-execute actions from history Reproduce specific scenarios
State restoration Jump to any historical state Debug specific state configurations
Undo/Redo Navigate back and forward in time User-facing time travel features

Example: Custom Time-Travel Hook

// useTimeTravel.js
import { useReducer, useCallback, useRef } from 'react';

function timeTravelReducer(history, action) {
  const { past, present, future } = history;

  switch (action.type) {
    case 'UNDO':
      if (past.length === 0) return history;
      return {
        past: past.slice(0, past.length - 1),
        present: past[past.length - 1],
        future: [present, ...future]
      };

    case 'REDO':
      if (future.length === 0) return history;
      return {
        past: [...past, present],
        present: future[0],
        future: future.slice(1)
      };

    case 'SET':
      if (action.payload === present) return history;
      return {
        past: [...past, present],
        present: action.payload,
        future: []
      };

    case 'RESET':
      return {
        past: [],
        present: action.payload,
        future: []
      };

    case 'JUMP_TO':
      // Jump to specific index in history
      const index = action.payload;
      const allStates = [...past, present, ...future];
      if (index < 0 || index >= allStates.length) return history;
      
      return {
        past: allStates.slice(0, index),
        present: allStates[index],
        future: allStates.slice(index + 1)
      };

    default:
      return history;
  }
}

export function useTimeTravel(initialState, maxHistory = 50) {
  const [history, dispatch] = useReducer(timeTravelReducer, {
    past: [],
    present: initialState,
    future: []
  });

  const { past, present, future } = history;

  const setState = useCallback((newState) => {
    dispatch({ type: 'SET', payload: newState });
  }, []);

  const undo = useCallback(() => {
    dispatch({ type: 'UNDO' });
  }, []);

  const redo = useCallback(() => {
    dispatch({ type: 'REDO' });
  }, []);

  const reset = useCallback((state = initialState) => {
    dispatch({ type: 'RESET', payload: state });
  }, [initialState]);

  const jumpTo = useCallback((index) => {
    dispatch({ type: 'JUMP_TO', payload: index });
  }, []);

  const canUndo = past.length > 0;
  const canRedo = future.length > 0;
  
  // Get all history for visualization
  const allHistory = [...past, present, ...future];
  const currentIndex = past.length;

  return {
    state: present,
    setState,
    undo,
    redo,
    reset,
    jumpTo,
    canUndo,
    canRedo,
    history: allHistory,
    currentIndex
  };
}

// Usage
function DrawingApp() {
  const {
    state: canvas,
    setState: setCanvas,
    undo,
    redo,
    canUndo,
    canRedo,
    history,
    currentIndex,
    jumpTo
  } = useTimeTravel({ shapes: [] });

  const addShape = (shape) => {
    setCanvas({ shapes: [...canvas.shapes, shape] });
  };

  return (
    <div>
      <div>
        <button onClick={undo} disabled={!canUndo}>
          ā¬…ļø Undo
        </button>
        <button onClick={redo} disabled={!canRedo}>
          āž”ļø Redo
        </button>
      </div>

      {/* Timeline visualization */}
      <div style={{ display: 'flex', gap: '4px' }}>
        {history.map((state, index) => (
          <button
            key={index}
            onClick={() => jumpTo(index)}
            style={{
              background: index === currentIndex ? 'blue' : 'gray',
              width: '20px',
              height: '20px'
            }}
            title={`State ${index}: ${state.shapes.length} shapes`}
          />
        ))}
      </div>

      <Canvas shapes={canvas.shapes} onAddShape={addShape} />
    </div>
  );
}

Example: Redux DevTools Time-Travel Integration

// Using Redux DevTools for automatic time-travel
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';

export const store = configureStore({
  reducer: rootReducer,
  devTools: {
    name: 'My App',
    features: {
      jump: true, // Enable jumping to any action
      skip: true, // Skip (ignore) actions
      reorder: true, // Reorder actions
      pause: true // Pause action recording
    }
  }
});

// In your component, Redux DevTools provides:
// 1. Timeline slider to scrub through states
// 2. Jump to any previous action
// 3. Skip/ignore specific actions
// 4. Replay action sequences

// You can also access DevTools programmatically:
function DebugPanel() {
  const exportState = () => {
    const state = store.getState();
    const blob = new Blob([JSON.stringify(state, null, 2)], {
      type: 'application/json'
    });
    // Download state snapshot
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `app-state-${Date.now()}.json`;
    a.click();
  };

  const importState = (stateJson) => {
    const state = JSON.parse(stateJson);
    // Restore state through DevTools
    window.__REDUX_DEVTOOLS_EXTENSION__?.send(
      { type: '@@IMPORT_STATE' },
      state
    );
  };

  return (
    <div>
      <button onClick={exportState}>Export Current State</button>
      <input
        type="file"
        accept=".json"
        onChange={(e) => {
          const file = e.target.files[0];
          file.text().then(importState);
        }}
      />
    </div>
  );
}
Note: Time-travel debugging works best with immutable state updates. Ensure all state changes create new objects rather than mutating existing ones for accurate history tracking.

5. Performance Profiling for State Updates

Profile and measure state update performance to identify bottlenecks and optimize re-renders.

Profiling Tool Description Use Case
React Profiler Record component render times Identify slow components
Performance API Measure precise timing with marks Custom performance tracking
Why Did You Render Library to detect unnecessary renders Find optimization opportunities
Chrome DevTools Timeline recording and flame graphs Analyze render performance

Example: React Profiler Component for State Updates

// ProfiledComponent.jsx
import { Profiler } from 'react';

function onRenderCallback(
  id, // The "id" prop of the Profiler tree
  phase, // "mount" or "update"
  actualDuration, // Time spent rendering
  baseDuration, // Estimated time without memoization
  startTime, // When React began rendering
  commitTime, // When React committed the update
  interactions // Set of interactions for this update
) {
  console.log({
    id,
    phase,
    actualDuration,
    baseDuration,
    renderTime: commitTime - startTime,
    interactions
  });

  // Send to analytics in production
  if (actualDuration > 16) { // Slower than 60fps
    console.warn(`āš ļø Slow render in ${id}: ${actualDuration}ms`);
    // Send to monitoring service
    sendToAnalytics({
      type: 'slow_render',
      component: id,
      duration: actualDuration,
      phase
    });
  }
}

export function ProfiledApp() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <App />
    </Profiler>
  );
}

// Profile specific components with state
function TodoList() {
  const [todos, setTodos] = useState([]);

  return (
    <Profiler id="TodoList" onRender={onRenderCallback}>
      <div>
        {todos.map(todo => (
          <Profiler key={todo.id} id={`Todo-${todo.id}`} onRender={onRenderCallback}>
            <TodoItem todo={todo} />
          </Profiler>
        ))}
      </div>
    </Profiler>
  );
}

Example: Custom Performance Monitoring Hook

// usePerformanceMonitor.js
import { useEffect, useRef } from 'react';

export function usePerformanceMonitor(componentName, dependencies = []) {
  const renderCount = useRef(0);
  const renderTimes = useRef([]);
  const lastRenderTime = useRef(performance.now());

  useEffect(() => {
    renderCount.current += 1;
    const now = performance.now();
    const renderTime = now - lastRenderTime.current;
    renderTimes.current.push(renderTime);

    // Keep only last 100 render times
    if (renderTimes.current.length > 100) {
      renderTimes.current.shift();
    }

    lastRenderTime.current = now;

    // Calculate statistics
    const avgRenderTime =
      renderTimes.current.reduce((a, b) => a + b, 0) / renderTimes.current.length;
    const maxRenderTime = Math.max(...renderTimes.current);

    console.log(`šŸ“Š ${componentName} Performance`, {
      renderCount: renderCount.current,
      lastRenderTime: renderTime.toFixed(2) + 'ms',
      avgRenderTime: avgRenderTime.toFixed(2) + 'ms',
      maxRenderTime: maxRenderTime.toFixed(2) + 'ms',
      dependencies
    });
  });

  return {
    renderCount: renderCount.current,
    avgRenderTime:
      renderTimes.current.reduce((a, b) => a + b, 0) / renderTimes.current.length
  };
}

// useWhyDidYouUpdate - Track which props/state caused re-render
export function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef();

  useEffect(() => {
    if (previousProps.current) {
      const allKeys = Object.keys({ ...previousProps.current, ...props });
      const changedProps = {};

      allKeys.forEach((key) => {
        if (previousProps.current[key] !== props[key]) {
          changedProps[key] = {
            from: previousProps.current[key],
            to: props[key]
          };
        }
      });

      if (Object.keys(changedProps).length > 0) {
        console.log(`šŸ”„ ${name} re-rendered due to:`, changedProps);
      }
    }

    previousProps.current = props;
  });
}

// Usage
function ExpensiveComponent({ data, filter, onUpdate }) {
  usePerformanceMonitor('ExpensiveComponent', [data, filter]);
  useWhyDidYouUpdate('ExpensiveComponent', { data, filter, onUpdate });

  const filteredData = useMemo(
    () => data.filter(filter),
    [data, filter]
  );

  return (
    <div>
      {filteredData.map(item => (
        <Item key={item.id} item={item} />
      ))}
    </div>
  );
}

Example: Redux Performance Monitoring Middleware

// performanceMiddleware.js
const performanceMiddleware = (store) => (next) => (action) => {
  const startTime = performance.now();
  const startMark = `action-${action.type}-start`;
  const endMark = `action-${action.type}-end`;

  // Mark start
  performance.mark(startMark);

  // Execute action
  const result = next(action);

  // Mark end
  performance.mark(endMark);

  // Measure duration
  const measureName = `action-${action.type}`;
  performance.measure(measureName, startMark, endMark);

  const endTime = performance.now();
  const duration = endTime - startTime;

  // Get performance entry
  const entries = performance.getEntriesByName(measureName);
  const entry = entries[entries.length - 1];

  // Log slow actions
  if (duration > 16) { // Slower than one frame (60fps)
    console.warn('āš ļø Slow action detected:', {
      action: action.type,
      duration: duration.toFixed(2) + 'ms',
      payload: action.payload
    });
  }

  // Aggregate statistics
  if (!window.__ACTION_STATS__) {
    window.__ACTION_STATS__ = {};
  }

  if (!window.__ACTION_STATS__[action.type]) {
    window.__ACTION_STATS__[action.type] = {
      count: 0,
      totalTime: 0,
      minTime: Infinity,
      maxTime: 0
    };
  }

  const stats = window.__ACTION_STATS__[action.type];
  stats.count += 1;
  stats.totalTime += duration;
  stats.minTime = Math.min(stats.minTime, duration);
  stats.maxTime = Math.max(stats.maxTime, duration);
  stats.avgTime = stats.totalTime / stats.count;

  // Clean up performance marks
  performance.clearMarks(startMark);
  performance.clearMarks(endMark);
  performance.clearMeasures(measureName);

  return result;
};

// View statistics in console
window.viewActionStats = () => {
  console.table(
    Object.entries(window.__ACTION_STATS__).map(([type, stats]) => ({
      Action: type,
      Count: stats.count,
      'Avg (ms)': stats.avgTime.toFixed(2),
      'Min (ms)': stats.minTime.toFixed(2),
      'Max (ms)': stats.maxTime.toFixed(2),
      'Total (ms)': stats.totalTime.toFixed(2)
    }))
  );
};

// Usage: Run viewActionStats() in console to see performance breakdown
Note: Use React DevTools Profiler tab to record and analyze render performance visually. Look for components with long render times or frequent unnecessary re-renders.

6. Custom Debug Hooks for State Monitoring

Create reusable debug hooks to monitor state changes, track renders, and identify performance issues.

Debug Hook Purpose Use Case
useDebugValue Label custom hooks in DevTools Display formatted debug info
useTraceUpdate Log which props/state changed Find cause of re-renders
useRenderCount Count component renders Detect excessive re-renders
useStateLogger Log all state changes Track state evolution

Example: Comprehensive Debug Hook Collection

// debugHooks.js
import { useRef, useEffect, useDebugValue } from 'react';

// 1. useTraceUpdate - Find what caused re-render
export function useTraceUpdate(props, componentName = 'Component') {
  const prev = useRef(props);

  useEffect(() => {
    const changedProps = Object.entries(props).reduce((acc, [key, value]) => {
      if (prev.current[key] !== value) {
        acc[key] = { from: prev.current[key], to: value };
      }
      return acc;
    }, {});

    if (Object.keys(changedProps).length > 0) {
      console.log(`[${componentName}] Changed props:`, changedProps);
    }

    prev.current = props;
  });
}

// 2. useRenderCount - Count renders
export function useRenderCount(componentName = 'Component') {
  const renders = useRef(0);
  renders.current += 1;

  useEffect(() => {
    console.log(`[${componentName}] Render #${renders.current}`);
  });

  useDebugValue(`Rendered ${renders.current} times`);

  return renders.current;
}

// 3. useStateWithHistory - State with history tracking
export function useStateWithHistory(initialValue, capacity = 10) {
  const [value, setValue] = useState(initialValue);
  const history = useRef([initialValue]);
  const pointer = useRef(0);

  const set = useCallback((newValue) => {
    const resolvedValue = typeof newValue === 'function' 
      ? newValue(value) 
      : newValue;

    if (history.current[pointer.current] !== resolvedValue) {
      // Remove future history if we're not at the end
      if (pointer.current < history.current.length - 1) {
        history.current.splice(pointer.current + 1);
      }

      history.current.push(resolvedValue);

      // Limit history size
      if (history.current.length > capacity) {
        history.current.shift();
      } else {
        pointer.current += 1;
      }

      setValue(resolvedValue);
    }
  }, [value, capacity]);

  const back = useCallback(() => {
    if (pointer.current > 0) {
      pointer.current -= 1;
      setValue(history.current[pointer.current]);
    }
  }, []);

  const forward = useCallback(() => {
    if (pointer.current < history.current.length - 1) {
      pointer.current += 1;
      setValue(history.current[pointer.current]);
    }
  }, []);

  const go = useCallback((index) => {
    if (index >= 0 && index < history.current.length) {
      pointer.current = index;
      setValue(history.current[index]);
    }
  }, []);

  useDebugValue(`History: ${pointer.current + 1}/${history.current.length}`);

  return {
    value,
    setValue: set,
    history: history.current,
    pointer: pointer.current,
    back,
    forward,
    go,
    canGoBack: pointer.current > 0,
    canGoForward: pointer.current < history.current.length - 1
  };
}

// 4. useDebugState - Enhanced useState with logging
export function useDebugState(initialValue, name = 'State') {
  const [value, setValue] = useState(initialValue);
  const renders = useRef(0);

  renders.current += 1;

  const debugSetValue = useCallback((newValue) => {
    const resolvedValue = typeof newValue === 'function' 
      ? newValue(value) 
      : newValue;

    console.log(`[${name}] State change:`, {
      from: value,
      to: resolvedValue,
      render: renders.current
    });

    setValue(resolvedValue);
  }, [value, name]);

  useDebugValue(`${name}: ${JSON.stringify(value)}`);

  return [value, debugSetValue];
}

// 5. useEffectDebugger - Debug useEffect dependencies
export function useEffectDebugger(effectFn, dependencies, name = 'Effect') {
  const previousDeps = useRef(dependencies);

  useEffect(() => {
    const changedDeps = dependencies.reduce((acc, dep, index) => {
      if (dep !== previousDeps.current[index]) {
        acc.push({
          index,
          before: previousDeps.current[index],
          after: dep
        });
      }
      return acc;
    }, []);

    if (changedDeps.length > 0) {
      console.log(`[${name}] Effect triggered by:`, changedDeps);
    }

    previousDeps.current = dependencies;

    return effectFn();
  }, dependencies);
}

// 6. useComponentDidMount - Track mount/unmount
export function useComponentDidMount(componentName = 'Component') {
  useEffect(() => {
    console.log(`āœ… [${componentName}] Mounted`);
    return () => {
      console.log(`āŒ [${componentName}] Unmounted`);
    };
  }, [componentName]);
}

Example: Using Debug Hooks in Components

// UserProfile.jsx
import {
  useTraceUpdate,
  useRenderCount,
  useDebugState,
  useEffectDebugger,
  useComponentDidMount
} from './debugHooks';

function UserProfile({ userId, theme, onUpdate }) {
  // Track what causes re-renders
  useTraceUpdate({ userId, theme, onUpdate }, 'UserProfile');

  // Count renders
  const renderCount = useRenderCount('UserProfile');

  // Track mount/unmount
  useComponentDidMount('UserProfile');

  // Debug state changes
  const [user, setUser] = useDebugState(null, 'User');
  const [loading, setLoading] = useDebugState(true, 'Loading');

  // Debug effect dependencies
  useEffectDebugger(
    () => {
      const fetchUser = async () => {
        setLoading(true);
        const data = await fetch(`/api/users/${userId}`).then(r => r.json());
        setUser(data);
        setLoading(false);
      };
      fetchUser();
    },
    [userId],
    'FetchUser'
  );

  if (loading) return <div>Loading...</div>;

  return (
    <div className={theme}>
      <h1>{user?.name}</h1>
      <p>Render count: {renderCount}</p>
    </div>
  );
}

// Console output when userId changes:
// [UserProfile] Changed props: { userId: { from: 1, to: 2 } }
// [UserProfile] Render #2
// [FetchUser] Effect triggered by: [{ index: 0, before: 1, after: 2 }]
// [Loading] State change: { from: false, to: true, render: 2 }
// [User] State change: { from: {...}, to: {...}, render: 3 }
// [Loading] State change: { from: true, to: false, render: 3 }

Example: Production-Safe Debug Wrapper

// Production-safe debug hooks that only run in development
const isDevelopment = process.env.NODE_ENV === 'development';

export const useDebugHooks = {
  useTraceUpdate: isDevelopment 
    ? useTraceUpdate 
    : () => {},
  
  useRenderCount: isDevelopment 
    ? useRenderCount 
    : () => 0,
  
  useDebugState: isDevelopment 
    ? useDebugState 
    : useState,
  
  useEffectDebugger: isDevelopment 
    ? useEffectDebugger 
    : useEffect,
  
  useComponentDidMount: isDevelopment 
    ? useComponentDidMount 
    : () => {}
};

// Usage - automatically disabled in production
import { useDebugHooks } from './debugHooks';

function MyComponent(props) {
  useDebugHooks.useTraceUpdate(props, 'MyComponent');
  const renderCount = useDebugHooks.useRenderCount('MyComponent');
  const [state, setState] = useDebugHooks.useDebugState(0, 'Counter');

  // These hooks do nothing in production builds
  return <div>Count: {state}</div>;
}
Note: Use useDebugValue in custom hooks to display formatted debug information in React DevTools. This helps identify hook values without console logging.

Section 18 Key Takeaways

  • React DevTools - Inspect component state, edit values in real-time, use Profiler to identify performance issues
  • Redux DevTools - Time-travel debugging, action history, state diff visualization, export/import state
  • State logging - Implement structured logging, sanitize sensitive data, disable verbose logging in production
  • Time-travel - Store state history, implement undo/redo, jump to any previous state for debugging
  • Performance profiling - Use React Profiler, track render times, identify slow actions with middleware
  • Debug hooks - Create reusable hooks for tracing updates, counting renders, logging state changes