How to Troubleshoot and Fix Common React Bugs
Diagnose unexpected renders, stale state, repeating Effects, and child crashes with a practical React debugging workflow and runnable fixes.
To troubleshoot a React bug, reproduce it, capture the exact console error and stack trace, inspect the component’s props and state in React Developer Tools, then classify the symptom before changing code. Most common problems come from impure rendering, mutated or redundant state, incorrectly designed Effects, or an unhandled child render error. Fix one cause at a time and repeat the same interaction.
Use the official React Developer Tools to inspect the component tree and profile updates. The workflow below covers the most common symptoms, explains how to distinguish development diagnostics from production defects, and includes runnable examples.
1. Start with evidence, not a guessed fix
- Reproduce the smallest failing interaction. Note the initial state, the action that triggers the issue, and what you expected to happen.
- Read the browser console. Copy the complete error or warning and follow the stack trace to the component or state setter involved. A React stack often identifies the responsible update more directly than the visible symptom.
- Inspect the live component. In React Developer Tools, use the Components panel to inspect props, state, and the rendered tree. Use Profiler when you need to understand which updates are repeated or expensive.
- Change one suspected cause. Repeat the same interaction and check both the console and the visible result.
React’s official Developer Tools guide describes the Components and Profiler panels. Avoid changing several unrelated things at once; that makes it harder to identify the cause.
2. Why is my React component rendering twice?
First check whether the extra render happens only in development with <StrictMode> enabled. Strict Mode deliberately performs extra development checks, including rendering components an additional time to help expose impure rendering. An extra development render by itself does not prove that production has a defect.
Render logic must be pure: given the same props, state, and context, a component should return the same output without changing external data. Do not mutate props or state, start subscriptions, write to the DOM, or trigger other side effects during render. See React’s Rules of React and Strict Mode documentation.
Example: move a side effect out of render
import { useEffect, useState } from 'react';
function Clock() {
const [now, setNow] = useState(() => new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
return <time>{now.toLocaleTimeString()}</time>;
}
export default Clock;
The timer is started in an Effect and stopped by its cleanup. Starting it in the component body would create a new timer during every render. In development, Strict Mode’s extra setup and cleanup checks help reveal missing cleanup.
If a component renders repeatedly, inspect what changes between renders: its state, props, context, and any parent updates. Use Profiler to find the update path. Do not assume every repeated render is a bug; focus on whether it produces incorrect behavior or unnecessary work.
3. Why is my useEffect running twice or looping?
An Effect synchronizes a component with an external system such as a timer, subscription, connection, or browser API. It is not a general mechanism for deriving one piece of React state from another. React may re-run Effects when dependencies change, and Strict Mode adds a development setup-and-cleanup check. A repeated Effect is a reason to inspect its purpose, dependencies, and cleanup.
Check the dependency list
Every reactive value used by an Effect belongs in its dependency list. If an Effect runs after every render, it may have no dependency list. If it loops, it may update state in a way that changes one of its dependencies on each run. Don’t omit dependencies to silence the symptom; restructure the Effect so it represents the actual synchronization.
import { useEffect, useState } from 'react';
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [error, setError] = useState('');
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
setError('');
const response = await fetch(
`/api/search?q=${encodeURIComponent(query)}`,
{ signal: controller.signal }
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
setResults(data.results);
} catch (err) {
if (err.name !== 'AbortError') setError(err.message);
}
}
load();
return () => controller.abort();
}, [query]);
if (error) return <p role="alert">Search failed: {error}</p>;
return <ul>{results.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
export default SearchResults;
This example synchronizes with a network request when query changes and aborts the previous request during cleanup. Adapt the URL and response shape to your API. Handling cancellation prevents an obsolete request from trying to update the component after a newer query has taken over.
Ask whether an Effect is needed
If there is no external system to synchronize, calculate derived values during rendering or handle the update in the event that caused it. For example, store a selected item’s ID and derive the item from the list rather than keeping both the ID and a second copy of the item in state. React’s guides on Synchronizing with Effects and You Might Not Need an Effect explain this distinction.
Do not hide a repeated Effect behind a “run once” ref as a substitute for cleanup. The setup should have a cleanup that undoes subscriptions, timers, connections, or other resources it creates.
4. Why is my React state not updating?
Check that the setter receives the intended next value, that you are not mutating the current object or array, and that the new value is actually different. React can skip an update when the next state is Object.is-equal to the current state. Also check whether the value can be derived from other state instead of stored twice.
Use immutable updates
import { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Read the error message', done: false }
]);
function toggleTodo(id) {
setTodos(current =>
current.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
}
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</label>
</li>
))}
</ul>
);
}
export default TodoList;
The update creates a new array and a new object for the changed todo. Mutating the existing state and passing the same reference back can cause React to ignore the update. React’s useState reference explains update identity and recommends following the console stack when the responsible setter is unclear.
Look for stale values and duplicated state
When the next state depends on the previous state, use the functional setter form: setCount(current => current + 1). This avoids basing an update on a value captured earlier by a callback. For multiple increments in one event, functional updates compose correctly.
Keep the minimum state needed to represent the interface. If fullName is always the concatenation of firstName and lastName, derive it while rendering rather than maintaining a third state value that can drift out of sync. See React’s Managing State guide.
5. How do I handle a child component crash?
A try/catch around a JSX return does not catch an error thrown later while React renders a child. Put an Error Boundary above the subtree that may fail. It can show fallback UI when a descendant throws during rendering.
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error('A child component failed to render:', error, info.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <p role="alert">This section could not be displayed.</p>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary fallback={<p role="alert">Unable to show the profile.</p>} >
<Profile />
</ErrorBoundary>
);
}
function Profile() {
return <h1>Profile</h1>;
}
export default App;
This boundary catches errors from descendants during rendering and related lifecycle work. It does not catch errors in event handlers or arbitrary asynchronous callbacks; handle those at the point where they occur. See React’s Error Boundary reference.
6. A symptom-to-cause checklist
| Symptom | Check first | Typical fix |
|---|---|---|
| Extra render in development | Strict Mode and render purity | Remove render-time mutation or side effects; confirm cleanup for external work. |
| Effect runs after every render | Missing or changing dependencies | List the reactive values used and restructure unstable logic where appropriate. |
| Effect repeatedly updates state | Whether the Effect is needed and whether its update changes a dependency | Derive internal values during render; reserve Effects for external synchronization. |
| State appears unchanged | Mutation, same-reference update, wrong setter, stale captured value | Use immutable updates and functional setters when next state depends on prior state. |
| Two UI values disagree | Redundant or duplicated state | Keep one source of truth and derive the other value. |
| A child crashes the page section | Render stack and boundary placement | Wrap the failure-prone subtree in an Error Boundary. |
7. Troubleshooting common React errors
“Too many re-renders”
Cause: A state setter is called during render, or an event prop calls a handler immediately, such as onClick={setOpen(true)}.
Fix: Move the state change into an event callback: onClick={() => setOpen(true)}. If the setter is in an Effect, check whether that Effect is necessary and whether it changes one of its own dependencies.
“Cannot update a component while rendering a different component”
Cause: A component triggers another component’s state update while React is rendering.
Fix: Move the update into an event handler or an Effect that synchronizes with an external system. Keep render pure.
Effect keeps firing
Cause: A dependency changes each render, a dependency is omitted and logic is out of sync, or the Effect updates state that changes a dependency.
Fix: Inspect the dependency values, remove unnecessary derived state, and make setup/cleanup symmetrical. Follow the Hooks lint guidance rather than suppressing dependency warnings.
State update does not appear
Cause: The existing object or array was mutated and the same reference passed to the setter, or the next value is equal to the current one.
Fix: Create a new object or array. Check the setter’s call site and the console stack.
Strict Mode appears to duplicate a request
Cause: Strict Mode performs an extra development setup and cleanup cycle for Effects to expose missing cleanup. A request may be started twice if its setup is not designed to tolerate the check.
Fix: Clean up or cancel obsolete work and make the synchronization correct when setup is repeated. First verify whether the behavior also occurs in a production build.
Error Boundary did not catch the failure
Cause: The error occurred in an event handler, an asynchronous callback, or outside the boundary’s descendant render work.
Fix: Handle event and promise errors in those paths directly. Place the boundary above the subtree whose rendering it should contain.
8. If the issue began after enabling React Compiler
Check for violations of the Rules of React and assumptions that object identity or memoization will remain stable. React’s Compiler debugging guide identifies unstable Effect dependencies and reliance on referential equality among patterns to investigate. Start with the component and hook where the behavior changed, then validate the smallest reproduction.
9. Performance, reliability, and cost notes
- Performance: Use the Profiler to identify work that matters before adding memoization. Fixing unnecessary Effects and duplicated state often removes avoidable updates at their source.
- Reliability: Cleanup makes subscriptions, timers, connections, and in-flight requests safe when dependencies change or a component unmounts. An Error Boundary contains descendant render failures so one broken section need not remove the surrounding interface.
- Debugging cost: Reproduce a small case and change one cause at a time. A minimal example and the complete console stack reduce time spent chasing symptoms. No prevalence or cost statistic is needed to use this process.
10. Or skip the browser setup
When the bug is a visual regression or a page needs a repeatable screenshot, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF; its options include custom waits, viewport and device presets, CSS, JavaScript, cookies, headers, and full-page capture. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo removes cookie banners, popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for free ScreenshotNeo screenshots.
11. FAQ
Should I turn off Strict Mode to stop duplicate renders?
Usually, investigate the behavior first. Strict Mode’s development checks can reveal impure render logic and missing Effect cleanup. Confirm whether the symptom represents incorrect production behavior before changing development safeguards.
Can I catch every React error with an Error Boundary?
No. Boundaries handle descendant rendering failures, but event handlers and asynchronous callbacks need their own error handling.
Should every state update go in an Effect?
No. Use event handlers for user actions, derive values that can be calculated from existing state, and use Effects to synchronize with external systems.
Which tool should I open first?
Start with the browser console and React Developer Tools Components panel. Use Profiler when you need to locate repeated or costly updates.


