React tells you a lot when something is wrong, if you know where to look. Most beginners hit the same dozen messages. This lesson explains what StrictMode does (and why your logs appear twice), walks through the messages you will see most often, and introduces the debugging tools.
StrictMode
The Vite template wraps <App /> in <StrictMode> in main.jsx, and the lesson editor does the same. StrictMode renders nothing itself; in development only it runs extra checks to help you find bugs early:
- Components render twice. React calls your component function twice and ignores one result. A pure component gives the same result both times; one that changes outside variables, or uses
Math.random()in its output, gives itself away (see the pure components lesson). - Effects run an extra time. After mounting, React runs each effect, then its cleanup, then the effect again. If your effect is written correctly, the extra cycle is harmless. If it forgot a cleanup (a timer, a listener, a connection), you will see duplicates immediately.
- Ref callbacks are called an extra time too.
- Warnings about deprecated APIs appear.
None of this happens in the production build, so users never see it. You can see it in action:
import { useEffect } from "react";
function App() {
console.log("render");
useEffect(() => {
console.log("effect: connect");
return () => console.log("cleanup: disconnect");
}, []);
return <p>Open the Console panel to see StrictMode at work.</p>;
}
export default App;Open the Console panel to see StrictMode at work.
CONSOLE
render
render
effect: connect
cleanup: disconnect
effect: connectTwo renders, then effect, cleanup, effect. Resist the temptation to remove StrictMode to "stop the double logs": that hides real bugs. If a double run breaks your code, the code needs fixing (add the cleanup; make render pure).
Read the error message
When React finds a problem it prints a message with the name of the component and, in development, a component stack (the chain of components that were rendering). Read the first line first: it usually says what is wrong. Then use the component stack to find where. Here are the most common messages.
"Too many re-renders"
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
return <button onClick={setCount(count + 1)}>Count: {count}</button>; // calls setCount during render
}
export default App;CONSOLE
warn: An error occurred in the <App> component.
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.setCount(count + 1) is called while rendering, because of the brackets. That sets state, which triggers a render, which calls it again, and React stops after too many nested updates. The fix: pass a function, onClick={() => setCount(count + 1)}. The same error appears for a setter called directly in the component body, and for an effect that sets state it depends on with no stopping condition.
"Objects are not valid as a React child"
function App() {
const user = { name: "Asha", joined: new Date(2026, 0, 15) };
return <p>User: {user}</p>;
}
export default App;CONSOLE
warn: An error occurred in the <p> component.
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.
Uncaught Error: Objects are not valid as a React child (found: object with keys {name, joined}). If you meant to render a collection of children, use an array instead.React can render strings, numbers and elements, not plain objects (or a Date). Render a property: {user.name}, or format the value: {user.joined.toLocaleDateString()}. Arrays of renderable items are fine.
"Each child in a list should have a unique key prop"
This warning, covered in the lists lesson, means that elements created in a map need a stable key. Use an id from the data, not the index, for lists that can change.
"A component is changing an uncontrolled input to be controlled"
import { useState } from "react";
function App() {
const [name, setName] = useState(); // undefined at the start
return <input value={name} onChange={e => setName(e.target.value)} />;
}
export default App;CONSOLE
error: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-componentsAn input with value={undefined} is uncontrolled; after the first keystroke value is a string, so it becomes controlled, which React warns about. Start the state with an empty string: useState(""). The reverse warning (controlled to uncontrolled) happens when the value becomes null or undefined later.
"Cannot read properties of undefined (reading 'x')"
Plain JavaScript, but common in React: you used data before it arrived. A user object is null while loading, and user.name crashes. Check first (user ? user.name : "..."), return early (if (!user) return null), or use optional chaining (user?.name).
"Rendered more hooks than during the previous render" / "Invalid hook call"
You broke the rules of hooks: a hook inside a condition, loop or nested function, or after an early return, or you called a hook outside a component. Move hooks to the top of the component, before any return. "Invalid hook call" can also mean two copies of React are installed in your project (often from a linked package); npm ls react shows duplicates.
"Warning: Can't perform a React state update on an unmounted component" and friends
An async callback (a fetch, a timer) set state after the component was removed. Cancel or ignore it in the effect's cleanup. React 18 and later no longer print the old warning, but the underlying problem (a stale response overwriting newer data) remains.
"Maximum update depth exceeded"
Similar to too many re-renders, usually from an effect that sets state and lists that state (or a new object) as a dependency. Fix the dependencies, or compute the value during render instead.
"X is not defined" / "X is not a function"
A missing import, a typo, a component imported with braces that was exported as default (or the reverse), or calling something that is undefined. Check the import line, and print the value.
"Hydration failed"
Only with server rendering: the HTML produced on the server differs from what the browser rendered. Typical causes are Date.now(), Math.random(), and reading window during render.
Debugging with the console
console.log is a perfectly good tool; use it well:
- Label what you print:
console.log("tasks:", tasks), notconsole.log(tasks). console.table(array)prints arrays of objects as a table.console.groupandconsole.groupEndnest related logs.- Remember snapshots: a log in an event handler shows that render's values, not the new state (see the snapshots lesson).
- StrictMode doubles logs from render. Log inside handlers and effects to see events once.
- Objects you log may show their current contents when you expand them later, not their contents at the time. Log
JSON.stringify(obj)or a copy if it matters.
The browser debugger
Open the browser's developer tools (F12 or Cmd+Option+I). In the Sources tab, find your file (Vite shows the original code through source maps), click a line number to set a breakpoint, and reload or trigger the action. Execution stops there; hover over variables, step over lines, and watch values in the side panel. You can also write the word debugger; in your code to stop there when the tools are open. A breakpoint inside a render function will hit twice per render in development because of StrictMode.
The Console tab shows errors with clickable links to the line, and the Network tab shows requests, statuses and responses, indispensable for data fetching problems (is the request sent? to which URL? what status? what JSON?).
React Developer Tools
Install the React Developer Tools extension for Chrome, Edge or Firefox. It adds two tabs to the developer tools:
- Components: the tree of your components, with the props, state, hooks and context of each. Select a component to see its values, and even edit them live to test a case. Search for components by name, and the eye icon in the browser inspector jumps from a page element to its component.
- Profiler: record an interaction and see which components rendered, why (props changed, state changed, parent rendered) and how long each took. This is how you find real performance problems before reaching for memoization.
Settings let you highlight components when they re-render, which makes unneeded renders visible at a glance.
A debugging routine
- Reproduce the problem reliably. Note exactly what you did.
- Read the first line of the error and the component named in it.
- Narrow it down: comment out parts, or replace data with a constant, until the problem disappears.
- Inspect state and props in the React Developer Tools, and requests in the Network tab.
- Form a hypothesis ("the id is a string here, not a number"), and test it with a log or breakpoint.
- Fix the cause, not the symptom, and add a test (next lesson) so that it cannot come back.
- If you are stuck for more than 30 minutes, explain the problem to someone (or write it down); you often find the answer while describing it.
Common mistakes
- Removing StrictMode to silence double logs or effects.
- Ignoring warnings because "it works". Warnings are early notices of future bugs.
- Reading only the last line of a long error; the first line is usually the clue.
- Debugging the production build without source maps.
- Logging state right after calling the setter and trusting the old value.
- Changing many things at once while debugging, so you never learn which change mattered.
Exercise
- Reproduce "Too many re-renders" on purpose in three different ways (setter in JSX, setter in the body, effect without dependencies) and fix each.
- Install React Developer Tools, inspect the state of one of your earlier components, and edit its state value from the Components tab.
- Set a breakpoint inside an event handler and step through it. Which values can you see that
console.logwould not show you? - Add a deliberate bug to your task manager (a missing key), find the warning, and fix it.
Key points
- StrictMode adds development-only checks: it renders components twice and runs effects, with their cleanup, an extra time, to expose impure code and missing cleanup.
- Most React errors have a clear message that names the component; read the first line, then the component stack.
- Too many re-renders, objects as children, missing keys and controlled/uncontrolled warnings each have a standard cause and fix.
- Use console.log, the browser's debugger with breakpoints, and the React Developer Tools to inspect components, props, state and render timings.