Skip to content

Quiz: Hooks Dependency Trap

intermediate12 min read

Test Your Dependency Array Intuition

Dependency arrays look simple. They are not. Each question below involves a useEffect dependency array. Don't guess — trace the closure behavior, Object.is comparisons, and effect timing.

If you score below 6 out of 8, go back to the useEffect, stale closures, and useMemo lessons. These are the bugs that will eat your afternoon.


Question 1: The Missing Dependency

function Timer({ delay }) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, delay);
    return () => clearInterval(id);
  }, [delay]);

  return <p>{count}</p>;
}
Quiz
If delay never changes, what does this Timer do?

Question 2: Object in Dependencies

function UserList({ filters }) {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetchUsers(filters).then(setUsers);
  }, [filters]);

  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

// Parent:
function App() {
  return <UserList filters={{ role: 'admin', active: true }} />;
}
Quiz
How often does the fetchUsers effect run?

Question 3: Function in Dependencies

function SearchResults({ query }) {
  const [results, setResults] = useState([]);

  function fetchResults() {
    return fetch(`/api/search?q=${query}`).then(r => r.json());
  }

  useEffect(() => {
    fetchResults().then(setResults);
  }, [fetchResults]);

  return <div>{results.length} results</div>;
}
Quiz
What happens with this component?

Question 4: Ref in Dependencies

function Component() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, [inputRef]);

  return <input ref={inputRef} />;
}
Quiz
Is putting inputRef in the dependency array correct?

Question 5: Stable Dispatch

function TodoApp() {
  const [todos, dispatch] = useReducer(todosReducer, []);

  useEffect(() => {
    const saved = JSON.parse(localStorage.getItem('todos') || '[]');
    if (saved.length) {
      dispatch({ type: 'LOAD', todos: saved });
    }
  }, [dispatch]);

  return <TodoList todos={todos} dispatch={dispatch} />;
}
Quiz
Is including dispatch in the dependency array necessary?

Question 6: Cleanup Timing

function Logger({ page }) {
  useEffect(() => {
    console.log(`Entered: ${page}`);

    return () => {
      console.log(`Left: ${page}`);
    };
  }, [page]);

  return <div>Page: {page}</div>;
}

// page changes: 'home' → 'about' → 'contact'
Quiz
What is the console output when page changes from 'home' to 'about' to 'contact'?

Question 7: Derived State Anti-pattern

function FilteredList({ items, query }) {
  const [filtered, setFiltered] = useState([]);

  useEffect(() => {
    setFiltered(items.filter(i => i.name.includes(query)));
  }, [items, query]);

  return <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}
Quiz
What is wrong with this pattern?

Question 8: The Double Subscription

function Chat({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    function handleMessage(msg) {
      setMessages(prev => [...prev, msg]);
    }

    const ws = new WebSocket(`wss://chat.example.com/${roomId}`);
    ws.addEventListener('message', handleMessage);

    return () => {
      ws.removeEventListener('message', handleMessage);
      ws.close();
    };
  }, [roomId]);

  return <MessageList messages={messages} />;
}
Quiz
In React Strict Mode (development), what happens on mount?

Scoring Guide

ScoreAssessment
8/8You understand effect lifecycle at a deep level. Ready for production React.
6-7Solid understanding with minor gaps. Review the scenarios you missed.
4-5Common patterns are shaky. Revisit useEffect, closures, and referential equality.
0-3Go back to the fundamentals. Focus on: closure capture, Object.is, and effect timing.