React DevTools and Debugging

1. React DevTools Browser Extension (Chrome, Firefox)

Feature Purpose Access Method Key Capabilities
Components Tab Inspect component tree and props/state Browser DevTools → React tab View hierarchy, search components, inspect props/state/hooks
Profiler Tab Measure rendering performance Browser DevTools → Profiler tab Record renders, flame graphs, ranked charts, commit details
Settings Configure DevTools behavior Gear icon in DevTools Highlight updates, trace updates, component filters, theme
Highlight Updates Visually show component re-renders Settings → Highlight updates Color-coded borders around re-rendering components
Strict Mode Badge Indicates Strict Mode enabled Component tree icons Shows which components wrapped in StrictMode
Component Filters Hide unwanted components from tree Settings → Component filters Filter by name, location, or HOC patterns

Example: Installing React DevTools

// Chrome Web Store
https://chrome.google.com/webstore (search "React Developer Tools")

// Firefox Add-ons
https://addons.mozilla.org (search "React Developer Tools")

// Edge Add-ons
https://microsoftedge.microsoft.com/addons (search "React Developer Tools")

// Standalone (Electron app)
npm install -g react-devtools
react-devtools

// Connect standalone to app
<script src="http://localhost:8097"></script>

Example: Accessing DevTools features

// Enable highlight updates to see re-renders
// 1. Open React DevTools
// 2. Click Settings (gear icon)
// 3. Check "Highlight updates when components render"
// 4. Watch colored borders appear on updates

// Search for components
// 1. Click in Components tab
// 2. Press Cmd+F (Mac) or Ctrl+F (Windows)
// 3. Type component name
// 4. Navigate with Enter/Shift+Enter

// View component source
// 1. Select component in tree
// 2. Right panel shows props, state, hooks
// 3. Click "<>" icon to view source code
// 4. Opens in Sources tab at component definition
DevTools Shortcuts: Cmd/Ctrl + F (search), Cmd/Ctrl + P (search by component name), Click component in page to select in tree (requires "Inspect" feature enabled), Click eye icon to inspect DOM node, Use filter input to narrow tree view.

2. Component Tree Inspector and Props Viewer

Inspector Feature Information Shown Interaction Use Case
Props Panel All props passed to component View values, edit primitives Debug incorrect prop values, test different prop combinations
State Panel Component state values View and edit state Test different states, reproduce bugs, validate state logic
Hooks Panel All hooks in component with values View hook state, effects Debug custom hooks, verify hook dependencies
Rendered By Parent component that rendered this component Click to navigate to parent Understand component hierarchy and data flow
Owners Component ownership chain Shows entire owner hierarchy Trace where props originated, debug context issues
Source Location File and line number of component Click to open in Sources tab Quick navigation to component code

Example: Editing props and state in DevTools

// Original component
function Counter() {
  const [count, setCount] = useState(0);
  return <div>Count: {count}</div>;
}

// In React DevTools:
// 1. Select Counter component
// 2. In right panel, see "State" section
// 3. Click on "count: 0" value
// 4. Edit to different value (e.g., 100)
// 5. Press Enter - component re-renders with new value

// Editing props (parent component needed)
function Parent() {
  return <Child name="Alice" age={25} />;
}

// In DevTools:
// 1. Select Child component
// 2. See "Props" section: name: "Alice", age: 25
// 3. Edit primitive values directly
// 4. Note: Complex objects/functions can't be edited

Example: Debugging hooks with DevTools

function UserProfile() {
  const [loading, setLoading] = useState(true);
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetchUser().then(setUser).catch(setError).finally(() => setLoading(false));
  }, []);
  
  const memoizedUser = useMemo(() => formatUser(user), [user]);
  const handleClick = useCallback(() => console.log(user), [user]);
  
  // ...
}

// In DevTools, Hooks section shows:
// State (0): true          // loading
// State (1): null          // user
// State (2): null          // error
// Effect                   // useEffect
// Memo                     // useMemo
// Callback                 // useCallback

// You can:
// - Edit state values directly
// - See dependencies for useMemo/useCallback
// - Track which effects ran
// - Identify custom hooks by name

Example: Using "Rendered by" to trace component tree

// Component hierarchy
<App>
  <Dashboard>
    <UserPanel>
      <UserAvatar src={user.avatar} />
    </UserPanel>
  </Dashboard>
</App>

// Select UserAvatar in DevTools
// "Rendered by" section shows:
// UserPanel → Dashboard → App

// Click any parent to navigate
// See full props chain:
// - App passes user to Dashboard
// - Dashboard passes user to UserPanel
// - UserPanel passes user.avatar to UserAvatar

3. React Profiler and Performance Analysis

Profiler Feature Visualization Metrics Analysis Use
Flamegraph Chart Hierarchical view of render times Render duration per component Identify slow components, bottlenecks in tree
Ranked Chart Sorted list of components by render time Duration, sorted slowest first Quickly find most expensive renders
Component Chart Timeline of individual component renders Each commit, duration, props/state changes Track re-render frequency, identify unnecessary renders
Commit Details Specific render commit information What triggered render, duration, timestamp Understand why component re-rendered
Interactions User interactions that triggered renders Which user actions caused updates Measure interaction responsiveness
Why Did This Render Explanation of render cause Props changed, state changed, parent rendered Debug unnecessary re-renders

Example: Using Profiler to identify performance issues

// Steps to profile your app:
// 1. Open React DevTools → Profiler tab
// 2. Click "Start Profiling" (blue record button)
// 3. Interact with your app (click buttons, type, navigate)
// 4. Click "Stop Profiling" (red square button)
// 5. Analyze the results

// Reading Flamegraph:
// - Wider bars = longer render time
// - Height = component depth in tree
// - Colors: Yellow (fast), Orange (medium), Red (slow)
// - Click bar to see component details

// Reading Ranked Chart:
// - Components sorted by render duration
// - Percentage shows relative time
// - Focus on top components first

// Example findings:
// Component: BigList
// Rendered: 15 times
// Duration: 1,245ms total
// Cause: Parent state change (unnecessary)
// Fix: Wrap in React.memo()

Example: Profiler API for custom measurements

import { Profiler } from 'react';

function onRenderCallback(
  id,        // "panel" - profiler ID
  phase,     // "mount" or "update"
  actualDuration,  // Time spent rendering
  baseDuration,    // Estimated time without memoization
  startTime,       // When render started
  commitTime,      // When React committed update
  interactions     // Set of interactions
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
  
  // Send to analytics
  analytics.track('component-render', {
    component: id,
    phase,
    duration: actualDuration,
    wasSlower: actualDuration > baseDuration
  });
}

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

// Nested profilers for granular tracking
function Dashboard() {
  return (
    <div>
      <Profiler id="sidebar" onRender={onRenderCallback}>
        <Sidebar />
      </Profiler>
      <Profiler id="content" onRender={onRenderCallback}>
        <Content />
      </Profiler>
    </div>
  );
}

Example: Analyzing "Why Did This Render"

// Enable in Settings → General → "Record why each component rendered"

// Example analysis in DevTools:
// Component: TodoItem
// Rendered because: Props changed
// Changed props:
//   - isCompleted: false → true
//   - onClick: [function] → [function] (different reference!)

// Problem identified: onClick recreated on every render
// Before (bad):
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  return todos.map(todo => (
    <TodoItem
      todo={todo}
      onClick={() => handleToggle(todo.id)} // New function every render!
    />
  ));
}

// After (good):
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(t => 
      t.id === id ? { ...t, completed: !t.completed } : t
    ));
  }, []);
  
  return todos.map(todo => (
    <TodoItem
      key={todo.id}
      todo={todo}
      onToggle={handleToggle} // Stable reference
    />
  ));
}

const TodoItem = React.memo(({ todo, onToggle }) => (
  <li onClick={() => onToggle(todo.id)}>{todo.text}</li>
));

4. State and Context Debugging Tools

Technique Tool/Method What to Check Common Issues Found
useState Inspection DevTools Hooks panel Current state values, update functions Stale closures, incorrect initial state, batching issues
useReducer Debugging DevTools + Redux DevTools State shape, action history Incorrect action handling, state mutations, missing cases
Context Values DevTools Context panel Current context value, provider location Missing provider, wrong value, too many re-renders
State History Custom logging, Redux DevTools State changes over time Unexpected mutations, missing updates
State Synchronization Multiple component inspection Shared state consistency Race conditions, stale data, caching issues
Derived State useMemo inspection Memoized values, dependencies Missing dependencies, over-computation

Example: Debugging context issues

// Context not updating problem
const UserContext = createContext();

function App() {
  const [user, setUser] = useState({ name: 'Alice', age: 25 });
  
  // Problem: New object every render!
  const value = { user, setUser };
  
  return (
    <UserContext.Provider value={value}>
      <Dashboard />
    </UserContext.Provider>
  );
}

// In DevTools:
// 1. Select any consumer component
// 2. Check "Context" in right panel
// 3. See value changing every render (object identity)
// 4. All consumers re-render unnecessarily

// Fix:
function App() {
  const [user, setUser] = useState({ name: 'Alice', age: 25 });
  
  const value = useMemo(() => ({ user, setUser }), [user]);
  
  return (
    <UserContext.Provider value={value}>
      <Dashboard />
    </UserContext.Provider>
  );
}

Example: Using Redux DevTools with useReducer

import { useReducer } from 'react';

// Install Redux DevTools Extension for full state tracking

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, action.payload];
    case 'REMOVE':
      return state.filter(todo => todo.id !== action.payload);
    case 'TOGGLE':
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    default:
      return state;
  }
}

// Connect useReducer to Redux DevTools
function useReducerWithDevTools(reducer, initialState, name) {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  useEffect(() => {
    if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
      const devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name });
      devTools.init(initialState);
      
      const wrappedDispatch = (action) => {
        dispatch(action);
        devTools.send(action, reducer(state, action));
      };
      
      return () => devTools.disconnect();
    }
  }, []);
  
  return [state, dispatch];
}

// Usage
function TodoApp() {
  const [todos, dispatch] = useReducerWithDevTools(
    todoReducer,
    [],
    'TodoApp'
  );
  
  // Now see all actions in Redux DevTools!
  // - Time-travel debugging
  // - Action history
  // - State diff
}

Example: Custom state debugging hooks

// Development-only state logger
function useStateWithLogger(initialState, name) {
  const [state, setState] = useState(initialState);
  
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      console.log(`[${name}] State changed:`, state);
    }
  }, [state, name]);
  
  return [state, setState];
}

// Track state change reasons
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('[why-did-you-update]', name, changedProps);
      }
    }
    
    previousProps.current = props;
  });
}

// Usage
function ExpensiveComponent(props) {
  useWhyDidYouUpdate('ExpensiveComponent', props);
  return <div>...</div>;
}

5. Time-travel Debugging and State History

Feature Capability Tool Use Case
Profiler Timeline Scrub through recorded renders React DevTools Profiler See component states at different render commits
Redux DevTools Jump to any action in history Redux DevTools Extension Time-travel through state changes, replay actions
React Query DevTools View cache history and mutations @tanstack/react-query-devtools Debug data fetching and caching issues
Custom History Track component lifecycle events Custom hooks with logging Create audit trail of state changes
Browser DevTools Record and replay interactions Chrome Recorder, Replay.io Reproduce bugs, share reproductions
Error Replay Record session before errors LogRocket, FullStory, Sentry Debug production errors with context

Example: Using Profiler for time-travel debugging

// Record interaction flow:
// 1. Open React DevTools → Profiler
// 2. Click Record
// 3. Perform user actions (form submit, navigation, etc.)
// 4. Click Stop
// 5. Use timeline scrubber to navigate commits

// Each commit shows:
// - Which components rendered
// - Why they rendered (props/state change)
// - Render duration
// - Component props/state at that moment

// Navigate commits:
// - Left/Right arrows to step through
// - Click specific commit in timeline
// - See exact component state at that point
// - Compare before/after states

Example: Redux DevTools time-travel

// Install Redux DevTools Extension

// Setup store with DevTools
import { createStore } from 'redux';

const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

// In Redux DevTools panel:
// 1. See all dispatched actions in order
// 2. Click any action to jump to that state
// 3. Use slider to scrub through action history
// 4. Toggle actions on/off to see impact
// 5. Export/import state snapshots
// 6. Replay actions at different speeds

// DevTools features:
// - Diff: See what changed in state
// - Test: Generate test code from actions
// - Export: Save state/action history as JSON
// - Import: Load previous session
// - Persist: Keep state across page reloads

Example: Building custom history tracker

// Create state history hook
function useStateHistory(initialState, maxHistory = 50) {
  const [state, setState] = useState(initialState);
  const [history, setHistory] = useState([initialState]);
  const [currentIndex, setCurrentIndex] = useState(0);
  
  const updateState = useCallback((newState) => {
    const value = typeof newState === 'function' ? newState(state) : newState;
    
    setHistory(prev => {
      const newHistory = prev.slice(0, currentIndex + 1);
      newHistory.push(value);
      return newHistory.slice(-maxHistory);
    });
    
    setCurrentIndex(prev => Math.min(prev + 1, maxHistory - 1));
    setState(value);
  }, [state, currentIndex, maxHistory]);
  
  const undo = useCallback(() => {
    if (currentIndex > 0) {
      const newIndex = currentIndex - 1;
      setCurrentIndex(newIndex);
      setState(history[newIndex]);
    }
  }, [currentIndex, history]);
  
  const redo = useCallback(() => {
    if (currentIndex < history.length - 1) {
      const newIndex = currentIndex + 1;
      setCurrentIndex(newIndex);
      setState(history[newIndex]);
    }
  }, [currentIndex, history]);
  
  const canUndo = currentIndex > 0;
  const canRedo = currentIndex < history.length - 1;
  
  return {
    state,
    setState: updateState,
    undo,
    redo,
    canUndo,
    canRedo,
    history,
    currentIndex
  };
}

// Usage - Text editor with undo/redo
function TextEditor() {
  const { 
    state: text, 
    setState: setText, 
    undo, 
    redo, 
    canUndo, 
    canRedo 
  } = useStateHistory('');
  
  return (
    <div>
      <button onClick={undo} disabled={!canUndo}>Undo</button>
      <button onClick={redo} disabled={!canRedo}>Redo</button>
      <textarea 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
      />
    </div>
  );
}

6. Production Debugging and Error Tracking (Source Maps)

Tool/Service Capabilities Key Features Best For
Sentry Error tracking, performance monitoring Error grouping, source maps, breadcrumbs, releases Production error tracking and alerting
LogRocket Session replay, error tracking Video replay, network logs, console logs, Redux state Visual debugging with full session context
Bugsnag Error monitoring and reporting Stability score, release tracking, user impact analysis Error management and prioritization
Datadog RUM Real user monitoring Performance metrics, session replay, error tracking, analytics Full-stack monitoring and observability
React Error Boundary Catch React errors in production Fallback UI, error reporting integration Graceful error handling in app
Source Maps Map minified code to source Readable stack traces in production Debug minified production code

Example: Sentry setup for React

// Install
npm install @sentry/react

// Initialize in entry point
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  integrations: [
    new BrowserTracing(),
  ],
  tracesSampleRate: 1.0, // Sample rate for performance
  environment: process.env.NODE_ENV,
  release: process.env.REACT_APP_VERSION,
  
  // Upload source maps for readable stack traces
  beforeSend(event, hint) {
    // Filter sensitive data
    if (event.request) {
      delete event.request.cookies;
    }
    return event;
  }
});

// Wrap app with Sentry ErrorBoundary
function App() {
  return (
    <Sentry.ErrorBoundary fallback={ErrorFallback} showDialog>
      <YourApp />
    </Sentry.ErrorBoundary>
  );
}

// Add user context
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username
});

// Add breadcrumbs for debugging
Sentry.addBreadcrumb({
  category: 'navigation',
  message: 'User navigated to dashboard',
  level: 'info'
});

// Manual error capture
try {
  riskyOperation();
} catch (error) {
  Sentry.captureException(error, {
    tags: {
      section: 'payment',
      severity: 'critical'
    },
    contexts: {
      transaction: {
        id: transactionId,
        amount: amount
      }
    }
  });
}

Example: LogRocket integration

// Install
npm install logrocket logrocket-react

// Initialize
import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';

LogRocket.init('YOUR_APP_ID');
setupLogRocketReact(LogRocket);

// Identify users
LogRocket.identify(user.id, {
  name: user.name,
  email: user.email,
  subscriptionType: user.plan
});

// Integration with Sentry
import * as Sentry from '@sentry/react';

LogRocket.getSessionURL(sessionURL => {
  Sentry.configureScope(scope => {
    scope.setExtra('sessionURL', sessionURL);
  });
});

// Track custom events
LogRocket.track('Purchase Completed', {
  productId: product.id,
  revenue: price,
  category: product.category
});

// Sanitize sensitive data
LogRocket.init('YOUR_APP_ID', {
  dom: {
    inputSanitizer: true, // Sanitize input values
  },
  network: {
    requestSanitizer: request => {
      // Remove sensitive headers
      if (request.headers['Authorization']) {
        request.headers['Authorization'] = 'REDACTED';
      }
      return request;
    },
    responseSanitizer: response => {
      // Remove sensitive response data
      if (response.body && response.body.creditCard) {
        response.body.creditCard = 'REDACTED';
      }
      return response;
    }
  }
});

Example: Production error boundary with reporting

import { Component } from 'react';
import * as Sentry from '@sentry/react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // Log to console in development
    if (process.env.NODE_ENV === 'development') {
      console.error('Error caught by boundary:', error, errorInfo);
    }
    
    // Report to error tracking service
    Sentry.captureException(error, {
      contexts: {
        react: {
          componentStack: errorInfo.componentStack
        }
      }
    });
    
    // Log to analytics
    analytics.track('error_boundary_triggered', {
      error: error.message,
      component: errorInfo.componentStack.split('\n')[1]
    });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div role="alert">
          <h2>Something went wrong</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
          </details>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

Example: Source map configuration for production debugging

// Create React App - .env.production
GENERATE_SOURCEMAP=true

// Upload source maps to Sentry
// package.json
{
  "scripts": {
    "build": "react-scripts build",
    "sentry:upload": "sentry-cli releases files $npm_package_version upload-sourcemaps ./build"
  }
}

// Webpack config (for custom setup)
module.exports = {
  devtool: 'source-map', // Generate source maps
  plugins: [
    new SentryWebpackPlugin({
      include: './build',
      ignoreFile: '.sentrycliignore',
      ignore: ['node_modules', 'webpack.config.js'],
      configFile: 'sentry.properties',
      release: process.env.REACT_APP_VERSION
    })
  ]
};

// .sentryignore
node_modules/
*.map

// Only upload source maps, don't serve them publicly
// Serve source maps only to authenticated users or keep them private
// Configure error tracking to fetch maps from your servers
Production Debugging Best Practices: Always use error boundaries, upload source maps to error tracking service, sanitize sensitive data before logging, add user context to errors, implement breadcrumbs for error context, monitor error rates and trends, set up alerts for critical errors, test error tracking in staging, use session replay for complex bugs, correlate errors with releases.

React Developer Tools and Debugging Summary