Skip to content

Troubleshooting

Fresh

Common 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() or Math.random() during render
  • Browser-only APIs like window.innerWidth in 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 here

Object/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:

  1. Open React DevTools > Profiler
  2. Enable "Highlight updates when components render"
  3. Interact with your app and observe which components flash
  4. Use React.memo() for components that re-render with same props
  5. Use useMemo/useCallback to stabilize values passed as props

Large Bundle Size

bash
# Analyze bundle
npx source-map-explorer build/static/js/*.js

Solutions:

  • Code-split with lazy() and dynamic import()
  • 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');
}, []);