Home JavaScript React Developer Tools and Debugging
Beginner 8 min · July 13, 2026

React Developer Tools and Debugging

React DevTools, component inspector, profiler, sourcemaps, and common debugging patterns..

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Everything here is grounded in real deployments.

Follow
Verified
production tested
July 13, 2026
last updated
2,018
articles · all by Naren
Before you start⏱ 36 min read
  • Node.js 18+, npm or yarn, React 18+, browser (Chrome or Firefox), basic understanding of React components, state, and hooks.
Quick Answer

React Developer Tools and Debugging: A core React concept for building modern user interfaces. It helps you structure your components efficiently and handle data flow predictably.

✦ Definition~90s read
What is React Developer Tools and Debugging?

React Developer Tools and Debugging is a fundamental concept in React development. It refers to the patterns, APIs, and best practices that React provides for building user interfaces. Understanding this concept is essential for writing clean, efficient, and maintainable React code. This tutorial covers everything from basic usage to advanced patterns with real-world examples.

React is a JavaScript library for building user interfaces.
Plain-English First

React is a JavaScript library for building user interfaces. This article covers devtools debugging — a key concept for building modern web applications.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

A comprehensive guide to react developer tools and debugging with production examples and best practices.

Why Debugging React Apps Is Different

Debugging a React application is fundamentally different from debugging plain JavaScript or jQuery. React's declarative, component-based architecture means state flows in one direction, and the DOM is a virtual representation of your component tree. When something breaks — a component doesn't re-render, state is stale, or props are missing — you can't just inspect the DOM and find the bug. You need tools that understand React's internals: the component hierarchy, hooks state, props, and the reconciliation process. Without these tools, you're flying blind. This section sets the stage for why React Developer Tools (React DevTools) are not optional; they are essential for any developer working on a React codebase, whether it's a small side project or a production app with thousands of components.

Counter.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
Output
Renders a simple counter. Without DevTools, you can't see the current state value or why re-renders happen.
Try it live
🔥React DevTools is a browser extension
Install it from Chrome Web Store or Firefox Add-ons. It adds a 'Components' and 'Profiler' tab to your browser's DevTools.
📊 Production Insight
In production, minified React removes component names. Use source maps and the 'Rendered by' feature to trace components back to source.
🎯 Key Takeaway
React DevTools gives you visibility into the component tree, state, and props — essential for debugging declarative UIs.
react-devtools-debugging THECODEFORGE.IO Debugging React Re-Renders Flow Step-by-step process to identify and fix unnecessary re-renders Identify Re-Render Trigger Check state updates, props changes, or context Use Profiler to Record Start recording in React DevTools Profiler Analyze Flamegraph Look for components re-rendering without changed props Apply React.memo or useMemo Memoize components or expensive calculations Verify Fix with Profiler Re-record to confirm reduced re-renders ⚠ Over-memoizing can hurt performance Only memoize when profiling shows actual benefit THECODEFORGE.IO
thecodeforge.io
React Devtools Debugging

Installing and Setting Up React DevTools

React DevTools is available as a browser extension for Chrome, Firefox, and Edge. For React Native, use the standalone app. After installation, you'll see two new tabs: 'Components' and 'Profiler'. The Components tab shows the component tree with props, state, and hooks. The Profiler tab records performance snapshots. For production debugging, you can also use the standalone version via npx react-devtools. Ensure your app uses React 16.8+ for full hooks support. If you're using React 18 with StrictMode, DevTools will show double renders intentionally — that's expected. Also, if you're using a framework like Next.js or Gatsby, you may need to open DevTools in a separate window to see the React tabs.

terminalBASH
1
2
3
4
# Install browser extension (Chrome)
# Go to chrome://extensions and enable Developer mode
# Or use the standalone version:
npx react-devtools
Output
Opens a standalone DevTools window that connects to your React app.
💡Check for React version
If you don't see the Components tab, your app might not be using React, or the extension is blocked. Try opening DevTools on a page like reactjs.org to verify it works.
📊 Production Insight
In production, the extension may not work if your app uses a Content Security Policy (CSP) that blocks the extension's script injection. Whitelist the extension's origin or use the standalone version.
🎯 Key Takeaway
Install the browser extension or use the standalone app. Verify it works on a known React site.

The Components tab is your primary debugging interface. It displays the component tree as a nested hierarchy. Click any component to inspect its props, state, and hooks. You can edit values in real-time — change a string prop or a number state and see the UI update instantly. The search bar filters components by name. The settings gear icon lets you toggle 'Highlight updates when components render' — useful for spotting unnecessary re-renders. You can also right-click a component and select 'Log component data to console' to inspect it programmatically. For class components, you'll see the instance; for functional components, you'll see hooks like useState and useEffect with their current values.

UserProfile.jsxJSX
1
2
3
4
5
6
7
import { useState } from 'react';

export default function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  // ... fetch user data
  return <div>{user?.name}</div>;
}
Output
In DevTools, click UserProfile to see props: { userId: 123 }, state: { user: { name: 'Alice' } }.
Try it live
💡Edit state directly
You can change state values in DevTools to test edge cases without modifying code. For example, set user to null to test loading state.
📊 Production Insight
Never rely on DevTools state edits for debugging production issues — they only affect your local session. Use logging or error boundaries for persistent debugging.
🎯 Key Takeaway
The Components tab shows the live component tree with editable props and state.
react-devtools-debugging THECODEFORGE.IO React DevTools Architecture Layers Component hierarchy and debugging tool integration Browser Extension React DevTools Panel | Components Tab | Profiler Tab React Fiber Tree Virtual DOM Nodes | Hooks State | Context Values Component Layer Functional Components | Class Components | Error Boundaries State Management useState | useReducer | Redux Store Side Effects & Network useEffect | Axios Calls | WebSocket Connections THECODEFORGE.IO
thecodeforge.io
React Devtools Debugging

Inspecting Hooks and State

Functional components rely on hooks like useState, useEffect, useReducer, and custom hooks. DevTools displays each hook in order, labeled by its type. For useState, you see the current value. For useEffect, you see the dependencies and whether the effect is pending. This is invaluable for debugging stale closures or missing dependencies. You can also see the values of custom hooks by expanding them. If a hook is not updating as expected, check its dependencies in DevTools. For example, if a useEffect has an empty dependency array but you expect it to run on prop change, you'll see the stale closure. DevTools also shows the 'rendered by' chain, helping you trace which parent caused a re-render.

Timer.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
12
import { useState, useEffect } from 'react';

export default function Timer({ start }) {
  const [seconds, setSeconds] = useState(start);
  useEffect(() => {
    const id = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []); // missing start dependency
  return <div>{seconds}s</div>;
}
Output
DevTools shows the useEffect with deps: []. If start changes, the effect won't re-run — you'll see the old closure.
Try it live
⚠ Stale closures are silent bugs
Always check that your useEffect dependencies match the variables used inside. DevTools shows the deps array — use it to verify.
📊 Production Insight
In production, a missing dependency can cause memory leaks or infinite loops. Use the exhaustive-deps ESLint rule and verify with DevTools.
🎯 Key Takeaway
DevTools exposes hook values and dependencies, making it easy to spot stale closures and missing deps.

Debugging Re-Renders with the Profiler

The Profiler tab records a performance snapshot of your app. Click the record button, interact with your app, then stop. You'll see a flamegraph showing each component's render duration. Red or yellow components are expensive. Click a component to see why it re-rendered: 'rendered because props changed', 'state changed', or 'parent re-rendered'. This is critical for optimizing performance. You can also filter by commit (each render cycle). The Profiler helps you identify unnecessary re-renders caused by passing new object/function references as props. Use React.memo, useMemo, and useCallback to reduce re-renders, but only after profiling — premature optimization is wasteful.

ExpensiveList.jsxJSX
1
2
3
4
5
6
7
8
9
10
import { memo } from 'react';

const ListItem = memo(({ item }) => {
  // expensive computation
  return <li>{item.name}</li>;
});

export default function List({ items }) {
  return <ul>{items.map(item => <ListItem key={item.id} item={item} />)}</ul>;
}
Output
Profiler shows ListItem re-renders only when item reference changes. Without memo, every parent re-render re-renders all items.
Try it live
🔥Profiler adds overhead
Only use the Profiler in development. In production, use React's built-in profiling build or third-party tools like why-did-you-render.
📊 Production Insight
A single unnecessary re-render in a list of 1000 items can cause jank. Profile before and after optimizations to measure impact.
🎯 Key Takeaway
The Profiler identifies expensive re-renders and their causes, guiding optimization efforts.

Using the Console and Source Maps Together

React DevTools works alongside the browser's console. You can right-click a component in the Components tab and select 'Store as global variable' to get a reference like $r. Then in the console, you can call $r.setState() (class components) or inspect $r.props. For functional components, $r gives you the fiber node — use $r.memoizedState to see hooks. Combine this with source maps to debug minified production code. If your production build has source maps enabled, you can set breakpoints in the original source. However, never deploy source maps to production unless you control access — they expose your code. Use error tracking tools like Sentry instead.

consoleJAVASCRIPT
1
2
3
4
5
// After storing component as $r
console.log($r.props);
console.log($r.state);
// For functional components:
console.log($r.memoizedState); // array of hook states
Output
Logs the component's props and state to the console for further inspection.
Try it live
⚠ Source maps in production
If you deploy source maps, anyone can view your original code. Use them only in staging or with authentication. For production, use error minification and external error tracking.
📊 Production Insight
We once had a production bug where a component's state was undefined. Using $r in the console on a staging environment with source maps revealed a missing initial state — saved hours of guesswork.
🎯 Key Takeaway
Combine DevTools with console and source maps for deep debugging, but be cautious with source maps in production.

Debugging Network Requests and Side Effects

Many React bugs stem from side effects: API calls in useEffect, subscriptions, or timers. DevTools doesn't show network requests directly, but you can use the browser's Network tab alongside it. When a component fails to load data, check the Network tab for failed requests. Then use DevTools to inspect the component's state — is the loading flag stuck? Is the error state set? For complex data fetching, consider using React Query or SWR, which provide DevTools extensions. For custom hooks, you can log inside them and see the logs in the console. Remember that useEffect runs after render, so if you set a breakpoint inside useEffect, the component has already rendered with initial state.

DataFetcher.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useState, useEffect } from 'react';

export default function DataFetcher() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData)
      .catch(setError);
  }, []);
  if (error) return <div>Error: {error.message}</div>;
  if (!data) return <div>Loading...</div>;
  return <div>{data.name}</div>;
}
Output
DevTools shows state: { data: null, error: null } initially, then updates after fetch.
Try it live
💡Use the Network tab
Open the Network tab before interacting. Filter by XHR/Fetch to see API calls. Check the response and status code.
📊 Production Insight
A common production issue: a useEffect with missing cleanup causes duplicate API calls. Use AbortController and check DevTools for pending effects.
🎯 Key Takeaway
Debug side effects by combining DevTools state inspection with the browser's Network tab.

Error Boundaries and Catching Crashes

React error boundaries catch JavaScript errors in the component tree and display a fallback UI. Without them, a single error crashes the entire app. DevTools shows error boundaries in the component tree. You can also simulate errors by throwing in a component. To create an error boundary, use a class component with componentDidCatch or the static getDerivedStateFromError. For functional components, you can use the react-error-boundary library. In DevTools, you'll see the error boundary component and its error state. This is crucial for production: wrap each major section of your app in an error boundary so a crash in one part doesn't take down the whole page.

ErrorBoundary.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Component } from 'react';

export default class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  componentDidCatch(error, info) {
    console.error('Error caught:', error, info);
  }
  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}
Output
When a child throws, DevTools shows ErrorBoundary with state: { hasError: true }.
Try it live
⚠ Error boundaries don't catch async errors
Errors in setTimeout, Promises, or event handlers are not caught. Use window.onerror or a global error handler for those.
📊 Production Insight
We wrap each route in an error boundary. When a third-party widget crashes, only that section shows a fallback — the rest of the app remains functional.
🎯 Key Takeaway
Error boundaries prevent full app crashes. DevTools helps you verify they work and inspect their state.

Debugging React with TypeScript and Prop Types

TypeScript and PropTypes catch many bugs at compile time, but runtime issues still occur. DevTools shows the actual prop values, which may differ from the declared types. For example, a prop might be undefined when you expected a string. Use DevTools to verify the prop values flowing into a component. If you're using TypeScript, you can also inspect the component's generic types via the console. For PropTypes, DevTools doesn't show warnings by default — you need to check the console. In development, React logs PropTypes warnings. In production, they are stripped. Always validate props at runtime in production using a library like io-ts or zod if needed.

Greeting.tsxTYPESCRIPT
1
2
3
4
5
6
7
8
interface GreetingProps {
  name: string;
  age?: number;
}

export default function Greeting({ name, age }: GreetingProps) {
  return <div>Hello {name}, age {age}</div>;
}
Output
DevTools shows props: { name: 'Alice', age: undefined } — the age prop is missing but optional.
Try it live
🔥TypeScript doesn't prevent runtime issues
TypeScript checks at compile time. At runtime, props can still be undefined or wrong type. Use DevTools to verify actual values.
📊 Production Insight
A common bug: a prop is passed as a string '123' instead of number 123. TypeScript doesn't catch this if the API returns strings. DevTools reveals the mismatch.
🎯 Key Takeaway
Use DevTools to inspect runtime prop values and catch type mismatches that TypeScript misses.

Performance Optimization with React.memo and useMemo

React.memo prevents re-renders if props haven't changed (shallow comparison). useMemo memoizes expensive computations. But misusing them can cause bugs: if you pass a new object/function as a prop, memo won't help. DevTools helps you verify: use the Profiler to see if a component re-renders when you expect it not to. Also, the Components tab shows whether a component is memoized (it will have a 'Memo' badge). For useMemo, you can see the memoized value in the hooks list. Remember: memoization has overhead. Only use it when profiling shows a performance problem. Premature optimization complicates code without benefit.

OptimizedList.jsxJSX
1
2
3
4
5
6
7
8
9
10
import { memo, useMemo } from 'react';

const List = memo(({ items }) => {
  return items.map(item => <div key={item.id}>{item.name}</div>);
});

export default function App({ data }) {
  const processed = useMemo(() => data.map(item => ({ ...item, name: item.name.toUpperCase() })), [data]);
  return <List items={processed} />;
}
Output
DevTools shows List as 'Memo' and processed as a memoized value. Profiler shows no re-render if data reference is stable.
Try it live
⚠ Memoization doesn't fix everything
If you pass an inline function as a prop, memo won't work. Use useCallback for functions. Always check with Profiler.
📊 Production Insight
We once memoized a component but forgot to memoize the callback prop. The Profiler showed no improvement. After using useCallback, re-renders dropped by 50%.
🎯 Key Takeaway
Use DevTools Profiler to verify that memoization actually prevents re-renders.

Debugging Third-Party Components and Libraries

Third-party components often come with their own state and lifecycle. DevTools shows them in the component tree, but their internals may be obscured if they are minified or use custom rendering. For example, a date picker library might render a complex tree of divs and spans. You can still inspect its props and state. If a library component is not behaving as expected, check the props you're passing. Also, some libraries like React Router or Redux have their own DevTools extensions. For Redux, use Redux DevTools alongside React DevTools. For React Router, you can inspect the router context via the component tree.

DatePickerUsage.jsxJSX
1
2
3
4
5
6
7
8
9
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';

export default function MyForm() {
  const [startDate, setStartDate] = useState(new Date());
  return (
    <DatePicker selected={startDate} onChange={date => setStartDate(date)} />
  );
}
Output
DevTools shows DatePicker component with props: { selected: Date, onChange: f }.
Try it live
🔥Library components may be wrapped
Some libraries export a HOC or forwardRef wrapper. DevTools shows the wrapper, not the inner component. Look for the 'rendered by' chain.
📊 Production Insight
A third-party table library was re-rendering the entire table on every keystroke. DevTools Profiler revealed the issue — we wrapped it in React.memo and saw a 90% performance improvement.
🎯 Key Takeaway
DevTools works with third-party components, but you may need to dig into the tree to find the actual component.

Common Pitfalls and How DevTools Helps

Common React bugs include: stale closures, missing keys in lists, mutating state directly, and infinite loops. DevTools helps diagnose all of them. For stale closures, inspect the hooks dependencies. For missing keys, the console warns, but DevTools shows the list of children — you can see if keys are missing. For direct state mutation, DevTools won't detect it, but you'll notice that the UI doesn't update — check if the state reference changed. For infinite loops, the Profiler will show many commits in a short time. Also, use the 'Highlight updates' feature to see which components re-render. If a component highlights unexpectedly, investigate its props and state.

BuggyList.jsxJSX
1
2
3
4
5
6
7
export default function BuggyList({ items }) {
  return (
    <ul>
      {items.map(item => <li>{item.name}</li>)} {/* missing key */}
    </ul>
  );
}
Output
Console warns: 'Each child in a list should have a unique "key" prop.' DevTools shows the list items without keys.
Try it live
⚠ Don't ignore console warnings
React logs warnings for missing keys, unstable props, etc. DevTools complements these warnings with visual inspection.
📊 Production Insight
A missing key in a list caused incorrect DOM updates in production, leading to broken UI after sorting. DevTools showed the list items without keys, and adding keys fixed it.
🎯 Key Takeaway
DevTools helps diagnose common React pitfalls by providing visibility into state, props, and re-renders.

When debugging complex React applications, component trees can become deep and wide, making it difficult to locate specific components. React DevTools provides powerful features to navigate large trees efficiently.

Search and Filter The Components tab includes a search bar at the top. You can type a component name (e.g., UserProfile) to filter the tree and highlight matching nodes. The search supports partial matches and case-insensitive queries. Additionally, you can filter by state or props by typing state:loading or props:userId. This helps isolate components with specific data.

Tree Collapsing Strategies By default, the component tree expands all nodes. For large trees, you can collapse all nodes by clicking the "Collapse All" button (folder icon) in the toolbar. To expand only the path to a selected component, right-click a component and choose "Expand All" or use the keyboard shortcut Ctrl+E (Cmd+E on Mac). You can also collapse/expand individual branches by clicking the arrow icons.

Practical Example Suppose you have a deeply nested list of items. Instead of scrolling through hundreds of nodes, search for ListItem to instantly see only those components. Then, use the "Expand All" on a specific ListItem to inspect its children.

These strategies drastically reduce visual clutter and speed up debugging, especially in production-like environments with large component hierarchies.

LargeTreeExample.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Example of a deeply nested component tree
function App() {
  return (
    <div>
      <Header />
      <MainContent>
        <Sidebar>
          <Navigation>
            <NavItem label="Home" />
            <NavItem label="Settings" />
          </Navigation>
        </Sidebar>
        <ArticleList>
          <Article title="First" />
          <Article title="Second" />
        </ArticleList>
      </MainContent>
      <Footer />
    </div>
  );
}
Try it live
💡Quick Filter by Component Type
📊 Production Insight
In large applications, encourage developers to use meaningful component names and consistent naming conventions, as this makes filtering in DevTools more effective.
🎯 Key Takeaway
Use search/filter and collapse/expand strategies in React DevTools to efficiently navigate large component trees and focus on relevant components.

Standalone DevTools for Safari and React Native

React DevTools can be used as a standalone application, which is essential for debugging React apps in Safari (where browser extensions are limited) and React Native environments. The standalone version communicates with the app via a WebSocket connection.

Setup for Safari 1. Install the standalone DevTools globally: npm install -g react-devtools. 2. Run the standalone app: react-devtools. 3. In your React app (running in Safari), ensure it's connected to the DevTools. For web apps, you need to set the REACT_DEBUGGER environment variable or use the __REACT_DEVTOOLS_GLOBAL_HOOK__ global. However, the simplest method is to use the browser extension if available; for Safari, you may need to use the standalone version and configure the app to connect.

Setup for React Native 1. Install react-devtools globally: npm install -g react-devtools. 2. Run react-devtools in your terminal. 3. In your React Native app, the DevTools automatically connect when running in development mode. If not, ensure the Metro bundler is running and the app is on the same network.

Port Configuration By default, the standalone DevTools listens on port 8097. You can change this with the --port flag: react-devtools --port=8098. Then, in your app, you need to specify the port when connecting. For React Native, you can set the REACT_DEVTOOLS_PORT environment variable or modify the connection URL in the Metro configuration.

Example: Custom Port ``bash react-devtools --port=9000 ` Then, in your React Native app, you might need to set window.__REACT_DEVTOOLS_GLOBAL_HOOK__.port = 9000` in development.

Using the standalone version ensures you have full debugging capabilities regardless of the browser or platform.

StandaloneSetup.shBASH
1
2
3
4
5
6
7
8
# Install standalone DevTools globally
npm install -g react-devtools

# Run with default port (8097)
react-devtools

# Run with custom port
react-devtools --port=9000
🔥Standalone vs Extension
📊 Production Insight
For React Native apps, always include react-devtools as a dev dependency and document the setup process for your team to ensure consistent debugging workflows.
🎯 Key Takeaway
Use the standalone React DevTools for debugging in Safari and React Native; configure the port if needed to avoid conflicts.
React DevTools vs Console Debugging Comparing approaches for debugging React applications React DevTools Console Debugging Component Tree Inspection Visual tree with props and state Manual console.log in each component Re-Render Detection Profiler shows flamegraph of re-renders Add console.count or useWhyDidYouUpdate Hooks State Viewing Directly see hooks values in Components Log state in useEffect or event handlers Performance Analysis Built-in Profiler with timing data Manual performance.now() measurements Error Source Location Source maps integrated with component st Stack trace from console.error THECODEFORGE.IO
thecodeforge.io
React Devtools Debugging

Prop Delta Debugging: Comparing Props Across Renders

Prop delta debugging helps identify why a component re-renders by comparing its props between consecutive renders. React DevTools does not have a built-in "prop diff" feature, but you can achieve this using the __REACT_DEVTOOLS_GLOBAL_HOOK__ or by leveraging the Profiler's recorded data.

Using the Profiler 1. Open the Profiler tab in React DevTools. 2. Start recording, perform an action that triggers re-renders, then stop recording. 3. Click on a component in the flamegraph. The right panel shows "Props" for that render. To compare, you need to manually note the props from one render and compare with another. This is tedious but works.

Manual Prop Comparison with console.log A more practical approach is to use useEffect or console.log to log props changes: ``jsx useEffect(() => { console.log('Props changed:', props); }, [props]); `` Then, in the browser console, you can see the logged props each time they change. However, this clutters the console.

Using a Custom Hook Create a custom hook useWhyDidYouUpdate that logs prop differences: ``jsx function useWhyDidYouUpdate(name, props) { const previousProps = useRef(props); useEffect(() => { const changes = {}; Object.keys({ ...previousProps.current, ...props }).forEach(key => { if (previousProps.current[key] !== props[key]) { changes[key] = { from: previousProps.current[key], to: props[key] }; } }); if (Object.keys(changes).length) { console.log([${name}] Props changed:, changes); } previousProps.current = props; }); } ` Use it in your component: useWhyDidYouUpdate('MyComponent', props);`

This method provides a clear delta of prop changes, helping you pinpoint unnecessary re-renders due to prop mutations or new object references.

useWhyDidYouUpdate.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { useRef, useEffect } from 'react';

function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef(props);

  useEffect(() => {
    const changes = {};
    const allKeys = Object.keys({ ...previousProps.current, ...props });
    allKeys.forEach(key => {
      if (previousProps.current[key] !== props[key]) {
        changes[key] = {
          from: previousProps.current[key],
          to: props[key]
        };
      }
    });

    if (Object.keys(changes).length) {
      console.log(`[${name}] Props changed:`, changes);
    }

    previousProps.current = props;
  });
}

export default useWhyDidYouUpdate;
Try it live
⚠ Performance Impact
📊 Production Insight
In production, avoid logging prop diffs. Instead, rely on React DevTools Profiler to identify re-render causes, as it has minimal overhead.
🎯 Key Takeaway
Prop delta debugging helps detect unnecessary re-renders by comparing props across renders; use a custom hook like useWhyDidYouUpdate for clear insights.
⚙ Quick Reference
15 commands from this guide
FileCommand / CodePurpose
Counter.jsxexport default function Counter() {Why Debugging React Apps Is Different
terminalnpx react-devtoolsInstalling and Setting Up React DevTools
UserProfile.jsxexport default function UserProfile({ userId }) {Navigating the Components Tab
Timer.jsxexport default function Timer({ start }) {Inspecting Hooks and State
ExpensiveList.jsxconst ListItem = memo(({ item }) => {Debugging Re-Renders with the Profiler
consoleconsole.log($r.props);Using the Console and Source Maps Together
DataFetcher.jsxexport default function DataFetcher() {Debugging Network Requests and Side Effects
ErrorBoundary.jsxexport default class ErrorBoundary extends Component {Error Boundaries and Catching Crashes
Greeting.tsxinterface GreetingProps {Debugging React with TypeScript and Prop Types
OptimizedList.jsxconst List = memo(({ items }) => {Performance Optimization with React.memo and useMemo
DatePickerUsage.jsxexport default function MyForm() {Debugging Third-Party Components and Libraries
BuggyList.jsxexport default function BuggyList({ items }) {Common Pitfalls and How DevTools Helps
LargeTreeExample.jsxfunction App() {Navigating Large Component Trees
StandaloneSetup.shnpm install -g react-devtoolsStandalone DevTools for Safari and React Native
useWhyDidYouUpdate.jsfunction useWhyDidYouUpdate(name, props) {Prop Delta Debugging

Key takeaways

1
React DevTools is essential
It provides visibility into the component tree, state, props, and hooks, making debugging declarative UIs possible.
2
Use the Profiler for performance
Identify unnecessary re-renders and optimize with memoization only after profiling confirms a bottleneck.
3
Combine DevTools with browser tools
Use the Network tab for API calls, console for logging, and source maps for stepping through code.
4
Error boundaries prevent crashes
Wrap sections of your app in error boundaries and use DevTools to verify they catch errors gracefully.

Common mistakes to avoid

3 patterns
×

Not understanding React component re-rendering

Fix
Use React.memo, useMemo, and useCallback strategically to prevent unnecessary re-renders.
×

Ignoring the rules of hooks

Fix
Always call hooks at the top level, not inside conditions, loops, or callbacks.
×

Mutating state directly instead of using setState

Fix
Always use the state setter function and treat state as immutable.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the Virtual DOM and how does React use it?
Q02JUNIOR
Explain the difference between state and props.
Q03JUNIOR
What is the purpose of the useEffect hook?
Q04JUNIOR
How does React handle keys in lists?
Q01 of 04JUNIOR

What is the Virtual DOM and how does React use it?

ANSWER
The Virtual DOM is a lightweight JavaScript representation of the real DOM. React diffs the Virtual DOM with the previous version and applies only the changed parts to the real DOM.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
How do I install React DevTools?
02
Why can't I see the Components tab in DevTools?
03
How can I debug a component that doesn't re-render when state changes?
04
What is the Profiler tab used for?
05
Can I use React DevTools in production?
06
How do I debug hooks with DevTools?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Everything here is grounded in real deployments.

Follow
Verified
production tested
July 13, 2026
last updated
2,018
articles · all by Naren
🔥

That's React. Mark it forged?

8 min read · try the examples if you haven't

Previous
Composition vs Inheritance
21 / 40 · React
Next
useState and useEffect Deep Dive