← Back to React Course | Chapter 5: Core Hooks | Lesson 12 of 12

Common Hook Mistakes

Common Hook mistakes are the same few banana peels almost every React beginner slips on at least once -- knowing them ahead of time helps you step around them.

Mistake: Missing Dependencies

When an effect, memo, or callback uses a value from component scope but doesn't list it in the dependency array, that value gets frozen at whatever it was on the render the Hook was first set up with -- a bug known as a stale closure.

Note: Let the ESLint react-hooks plugin's exhaustive-deps rule guide you -- it flags missing dependencies automatically.

Warning: A missing dependency doesn't cause an immediate error -- it silently produces outdated behavior that can be hard to notice until much later.

Example: Mistake: Missing Dependencies

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function StaleExample() {
      const [count, setCount] = React.useState(0);
      React.useEffect(() => {
        const id = setInterval(() => {
          console.log("Count is", count); // stale! always logs the initial value
        }, 1000);
        return () => clearInterval(id);
      }, []); // missing 'count' dependency
      return <button onClick={() => setCount(count + 1)}>{count}</button>;
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<StaleExample />);
  </script>
</body>
</html>

Mistake: Conditional Hook Calls

Putting a Hook call inside an if statement means it might be skipped on some renders but not others, breaking React's assumption that Hooks are called in the same order every time. This produces confusing errors that often don't clearly point back to the real cause.

Note: Move the conditional logic inside the Hook (like inside an effect body) rather than around the Hook call itself.

Warning: This mistake often produces the cryptic error 'Rendered more hooks than during the previous render.'

Example: Mistake: Conditional Hook Calls

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function FixedVersion({ showExtra }) {
      const [count, setCount] = React.useState(0); // always called
      const [extra, setExtra] = React.useState(0); // always called, even if unused
      return <button onClick={() => setCount(count + 1)}>{count}{showExtra ? ` (extra: ${extra})` : ""}</button>;
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<FixedVersion showExtra={true} />);
  </script>
</body>
</html>

Mistake: Mutating State Directly

Modifying an array or object in state directly (like calling .push() on a state array) can leave the reference unchanged, so React may not detect that anything changed and skip re-rendering. Always create a new copy when updating object or array state.

Note: Reach for the spread operator (...) or array methods that return new arrays (.map, .filter) whenever updating object/array state.

Warning: This mistake is especially sneaky because the data DID technically change in memory -- it's just that React's change-detection didn't notice.

Example: Mistake: Mutating State Directly

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function TagList() {
      const [tags, setTags] = React.useState(["react"]);
      function addTag() {
        setTags([...tags, "hooks"]); // correct: new array via spread
      }
      return (
        <div>
          <ul>{tags.map((t, i) => <li key={i}>{t}</li>)}</ul>
          <button onClick={addTag}>Add Tag</button>
        </div>
      );
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<TagList />);
  </script>
</body>
</html>
Common Mistakes
  1. Forgetting a dependency in useEffect/useMemo/useCallback's dependency array, leading to stale values.
  2. Calling Hooks conditionally, breaking React's required consistent call order.
  3. Directly mutating state (arrays/objects) instead of creating new copies before updating.
Chapter Summary
  • Most Hook bugs come from a small, well-known set of common mistakes.
  • Stale closures happen when a dependency is missing from an effect's array.
  • Conditional Hook calls violate the Rules of Hooks and cause runtime errors.
  • The ESLint react-hooks plugin catches many of these mistakes automatically.
Browser Support

These mistakes and their fixes apply to all Hooks since their introduction in React 16.8.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.