Appearance
Troubleshooting
FreshCommon React errors and how to fix them.
Rendering Errors
"Too many re-renders"
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
Cause: Setting state during render without a condition or calling a function instead of passing it.
jsx
// BAD: calls handleClick during render
<button onClick={handleClick()}>Click</button>
// GOOD: passes the function
<button onClick={handleClick}>Click</button>
<button onClick={() => handleClick(id)}>Click</button>"Cannot update a component while rendering a different component"
Cause: Setting state of a parent component during a child's render.
jsx
// BAD: setting parent state during child render
function Child({ onMount }) {
onMount(); // This calls setState on parent during render!
return <div>Child</div>;
}
// GOOD: use useEffect
function Child({ onMount }) {
useEffect(() => {
onMount();
}, [onMount]);
return <div>Child</div>;
}"Each child in a list should have a unique 'key' prop"
Fix: Add a unique key to each element in a map():
jsx
// BAD
items.map(item => <li>{item.name}</li>)
// GOOD
items.map(item => <li key={item.id}>{item.name}</li>)Don't use array index as key if items can be reordered, added, or removed.
Hydration Errors
"Text content does not match"
Cause: HTML generated on the server doesn't match what the client renders.
Common causes:
- Using
Date.now()orMath.random()during render - Browser-only APIs like
window.innerWidthin render - Different data on server vs client
jsx
// BAD: different on server and client
function Clock() {
return <p>{new Date().toLocaleTimeString()}</p>;
}
// GOOD: use useEffect for client-only values
function Clock() {
const [time, setTime] = useState(null);
useEffect(() => {
setTime(new Date().toLocaleTimeString());
const id = setInterval(() => setTime(new Date().toLocaleTimeString()), 1000);
return () => clearInterval(id);
}, []);
return <p>{time ?? 'Loading...'}</p>;
}"Expected server HTML to contain a matching element"
Fix: Ensure the HTML structure matches between server and client. Check for:
<p>inside<p>(invalid HTML)<div>inside<p>(invalid HTML)- Browser extensions injecting elements
State Issues
State Not Updating
State updates are asynchronous. You can't read the new value immediately:
jsx
function handleClick() {
setCount(count + 1);
console.log(count); // Still the old value!
}
// If you need the next value, store it in a variable
function handleClick() {
const nextCount = count + 1;
setCount(nextCount);
console.log(nextCount); // New value
}State Updates Not Batched
React automatically batches state updates inside event handlers. Outside of event handlers (setTimeout, promises, native events), use flushSync if you need synchronous updates:
jsx
import { flushSync } from 'react-dom';
// Rare case where you need immediate DOM update
flushSync(() => {
setCount(c => c + 1);
});
// DOM is updated hereObject/Array State Not Reflecting Changes
Always create new objects/arrays instead of mutating:
jsx
// BAD: mutating
person.name = 'New Name';
setPerson(person); // React sees same reference, skips re-render
// GOOD: new object
setPerson({ ...person, name: 'New Name' });Effect Issues
Effect Runs on Every Render
Cause: Object or function in dependency array is recreated each render.
jsx
// BAD: options is a new object every render
useEffect(() => {
connect(options);
}, [options]); // Always "changed"
// GOOD: move object creation inside effect
useEffect(() => {
const options = { serverUrl, roomId };
connect(options);
}, [serverUrl, roomId]);Effect Cleanup Not Running
Make sure you return the cleanup function:
jsx
// BAD: no cleanup
useEffect(() => {
const id = setInterval(() => console.log('tick'), 1000);
// Memory leak! Interval never cleared
});
// GOOD: return cleanup
useEffect(() => {
const id = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(id);
}, []);Stale Closure in Effect
jsx
// BAD: stale count
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // Always reads initial count
}, 1000);
return () => clearInterval(id);
}, []);
// GOOD: use updater function
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // Always reads latest
}, 1000);
return () => clearInterval(id);
}, []);Performance Issues
Slow Initial Render
- Use
React.lazy()and<Suspense>to code-split - Move expensive computations to
useMemo - Avoid creating new objects/arrays in render
- Profile with React DevTools Profiler
Unnecessary Re-renders
Use React DevTools to identify components re-rendering unnecessarily:
- Open React DevTools > Profiler
- Enable "Highlight updates when components render"
- Interact with your app and observe which components flash
- Use
React.memo()for components that re-render with same props - Use
useMemo/useCallbackto stabilize values passed as props
Large Bundle Size
bash
# Analyze bundle
npx source-map-explorer build/static/js/*.jsSolutions:
- Code-split with
lazy()and dynamicimport() - Tree-shake unused exports (most bundlers do this automatically)
- Use production build (
npm run build)
Common ESLint Warnings
"React Hook useEffect has a missing dependency"
The exhaustive-deps rule ensures effects re-run when their dependencies change:
jsx
// WARNING: missing dependency
useEffect(() => {
fetchData(userId);
}, []); // userId should be in deps
// FIX: add the dependency
useEffect(() => {
fetchData(userId);
}, [userId]);
// OR: if you truly want it to run once, restructure
useEffect(() => {
const user = getCurrentUser();
fetchData(user.id);
}, []);"React Hook useCallback/useMemo has unnecessary dependencies"
Remove dependencies that don't change or aren't used in the hook:
jsx
// WARNING: unnecessary dep
const handleClick = useCallback(() => {
console.log('clicked');
}, [someConstant]); // someConstant never changes
// FIX
const handleClick = useCallback(() => {
console.log('clicked');
}, []);