Debugging React Apps
React DevTools extension, reading component trees, inspecting props and state, understanding common React error messages, and tracing renders with console.log().
Chrome DevTools covers the browser layer — HTML, CSS, network requests. But React has its own internal structure — components, props, state — that DevTools doesn't show by default. For that, you need the React DevTools browser extension. Together, they give you complete visibility into everything happening in your app.
Why First: The Component With Stale Data
You built a job listing page. A recruiter marks a job as filled. They navigate back to the listing. The job still shows as "Available." You reload the page — it shows "Filled" correctly. So the database is correct. But the UI is showing old data.
This is a caching or state management problem. The question is: what state does your React component think it has?
Open React DevTools → Components tab → find the job listing component → look at its props and state. You will see the data it is currently holding. If it's holding the old is_filled: false value, you know the issue is in how you're invalidating the React Query cache after the mutation. If it's holding the correct is_filled: true, the problem is in how the component is rendering that value.
Without React DevTools, you would need to add console.log() statements and keep re-running. With React DevTools, you see the live state directly.
Installing React DevTools
React DevTools is a browser extension made by the React team (Facebook/Meta). It is not included in Chrome by default — you install it once and it stays.
Open Google Chrome and go to chrome.google.com/webstore
In the search bar, type React Developer Tools
Look for the result by Meta (or Facebook) — it is the official one, with a blue React logo icon and several million users
Click Add to Chrome, then Add extension in the confirmation dialog
The React logo icon will appear in your browser's extension area (top right, near the address bar)
Open your React app (your dev server at localhost:5173 or wherever it runs)
Open DevTools (F12). You will now see two new tabs: Components and Profiler
Indicator that it's working: When you visit a React app, the React DevTools icon in your toolbar turns from gray to colored (red in development mode, blue in production mode). On a non-React site, it stays gray.
The Components Tab
The Components tab shows you the component tree of your running React app — every component, nested in the same hierarchy as your code.
Browsing the Component Tree
You will see your root component at the top (often App), with all its children nested below it. Each component can be expanded to see its children. This is your app's structure made visible.
Hovering over a component: As you hover over component names in the tree, the corresponding element highlights on the page — just like the Elements tab. This helps you find which component is responsible for which part of the UI.
Clicking a component: Click any component in the tree to select it. On the right side of the panel, you will see:
- Props — all the values passed into this component from its parent
- State (for components using
useState) — the current value of each state variable - Hooks — the state and effects from custom hooks and React Query queries
Reading Props and State
When you click a component, its props appear as an expandable tree. For example, a JobCard component might show:
If the database says is_filled: true but the component shows is_filled: false, you have a stale data problem. The database is correct but React Query's cache hasn't been invalidated.
Inspecting React Query State
When your component uses a React Query hook (useQuery, useMutation), the hook's state appears under Hooks in the selected component's panel:
This tells you:
- Is the query in a loading, success, or error state right now?
- What data does it currently hold?
- Is it currently fetching in the background?
Finding a Component Quickly
Large apps have hundreds of components. Hunting through the tree is slow.
The search box: At the top of the Components tab, there is a search field. Type a component name (e.g., JobCard) and the tree filters to show only matching components. Click the result to select it.
The select button: In the Components tab toolbar, look for the "Select element" button (it looks like the cursor/crosshair icon). Click it, then click any element on the page. DevTools will find and select the React component responsible for that element. Much faster than hunting through the tree.
Editing Props and State Directly
In the Components panel, when you click on a prop or state value, you can edit it directly and the component re-renders immediately with the new value.
Example use case: Your isLoading prop is false and you want to see what your loading skeleton looks like. Click isLoading in the props panel, change it to true. The component immediately renders the loading state — no code change needed.
This is extremely useful for testing edge cases and different states without having to write code or reproduce specific conditions.
These edits are temporary — they disappear on page refresh. They are for exploration only, not permanent changes.
The Profiler Tab
The Profiler tab measures render performance — it tells you which components are re-rendering and how long each render takes.
Using the Profiler
Click the Profiler tab in React DevTools
Click the blue circle (record button) to start recording
Interact with your app — click buttons, type in inputs, navigate
Click the record button again to stop recording
The Profiler shows a flame chart of every render that happened during the recording
Reading the flame chart: Each bar represents a component render. Wide bars = longer renders. Bars that appear many times = components that re-rendered repeatedly. Gray bars = components that didn't re-render (good — they were skipped).
The "Why did this render?" column: Click any component in the profiler to see why it re-rendered — which prop or state change triggered it. This is how you find unnecessary re-renders.
Common React Console Error Messages
These are the errors you will see most often as a beginner. Learn to recognize them instantly.
"Cannot read properties of undefined (reading 'map')"
What it means: You are trying to call .map() on something that is undefined — usually because your data hasn't loaded yet.
Example:
Fix: Add optional chaining (?.) before .map(), or add a loading check before rendering.
"Each child in a list should have a unique key prop"
What it means: When you use .map() to render a list of elements, each element must have a key prop that is unique within the list. React uses this to efficiently update the list.
Example:
Fix: Add key={item.id} (or some other unique identifier) to the element returned by .map(). Never use the array index as the key (like key={index}) — it causes subtle bugs when the list order changes.
"Warning: An update to [Component] inside a test was not wrapped in act(...)"
What it means: An asynchronous state update happened after a test finished. Usually a cleanup issue in tests.
In practice for trainees: You will see this during development when a component tries to update state after it has been unmounted (for example, a network request completes but the user already navigated away from the page that made the request).
Fix: Add a check inside your async function before updating state, or use React Query instead of manual useEffect for data fetching — React Query handles this automatically.
"Warning: You provided a value prop to a form field without an onChange handler"
What it means: You made an input "controlled" (by setting its value prop) but didn't give React a way to update that value when the user types. The input will appear frozen — the user can't type in it.
Fix: Either add an onChange handler, or switch to defaultValue instead of value for an uncontrolled input.
Using console.log() Inside Components
For simpler debugging, console.log() inside a component tells you what it received and when it rendered.
Important: In React, components can render multiple times — on first load, when state changes, when parent re-renders. If you see JobCard rendering with job: printed 8 times for 4 jobs, that is normal (each card renders once, React renders twice in StrictMode during development).
What to log to trace a data flow:
This tells you at every render: Has the data arrived? Is there an error? What shape is the data?
The React DevTools Quick Reference
| What You Want to Know | Where to Look |
|---|---|
| What data does this component have? | Components tab → click component → Props |
| What state does this component hold? | Components tab → click component → Hooks / State |
| Why is this component re-rendering? | Profiler tab → record interaction → click component |
| Which component renders a specific element? | Components tab → Select button → click the element |
| Is my React Query data loading? | Components tab → click the component using useQuery → Hooks → Query |
| What does my loading skeleton look like? | Components tab → edit isLoading prop to true |