← Back to Chapters

Hook Dependency Arrays

⚛️ Hook Dependency Arrays

? Quick Overview

Many React Hooks — like useEffect, useMemo, and useCallback — accept a dependency array as their second argument. This array controls when the hook runs by listing all the values that the hook depends on.

If you understand dependency arrays well, you can avoid unnecessary re-renders, stale data, and performance issues in your React apps.

✅ Core idea: “Run this hook only when these values change.”

? What Is a Dependency Array?

The dependency array tells React: “Run this hook’s function only when one of these values changes.”

? View Code Example
// Effect runs only when 'count' changes
useEffect(() => {
  console.log("Effect runs when count changes");
}, [count]);

In this example, the effect re-runs only when count changes — not on every render.

? Key Concepts

  • Dependency array is always the second argument to hooks like useEffect, useMemo, and useCallback.
  • React compares the current dependency values with the previous ones.
  • If any dependency has changed (shallow comparison), the hook’s function runs again.
  • Leaving a dependency out can cause stale data or subtle bugs.
  • Adding too many dependencies can cause extra re-runs and performance costs.

? Syntax & Theory

General pattern for hooks with dependency arrays:

? View Code Example
// Generic pattern for a hook with dependencies
useEffect(() => {
  // side effect logic that uses a, b, c
}, [a, b, c]);

You must list every value that is used inside the hook’s callback and can change over time: props, state, memoized values, or functions.

? Example 1: useEffect Dependency

? View Code Example
// Effect depends only on 'count'
function Counter() {
  const [count, setCount] = React.useState(0);
  const [name, setName] = React.useState("");

  React.useEffect(() => {
    console.log("Effect triggered because 'count' changed");
  }, [count]);

  return (
    <div className="text-center">
      <input
        className="form-control w-50 mx-auto mb-2"
        placeholder="Enter name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <p>Count: {count}</p>
      <button
        className="btn btn-primary"
        onClick={() => setCount(count + 1)}
      >
        Increment
      </button>
    </div>
  );
}

? Live Output / Explanation

  • Typing in the name input updates state but does not trigger the effect.
  • Clicking Increment updates count and does trigger the effect.
  • The console logs each time count changes.

Conclusion: The effect depends only on count, not on name.

? Example 2: Empty Dependency Array

? View Code Example
// Effect runs only once after initial mount
useEffect(() => {
  console.log("Runs only once after mount");
}, []);

An empty dependency array means the hook runs only once on mount, similar to componentDidMount. Use this for one-time setup, like initial API calls or event listeners (with proper cleanup).

⚙️ Example 3: Multiple Dependencies

? View Code Example
// Effect re-runs when 'count' or 'name' changes
useEffect(() => {
  console.log("Effect depends on count and name");
}, [count, name]);

The effect runs whenever any one of the listed dependencies changes. If either count or name updates, the effect will run again.

? Hook Dependency Summary

Hook Purpose of Dependency Array
useEffect Controls when side effects re-run
useMemo Controls when computed values are recalculated
useCallback Controls when memoized functions are recreated

? Example 4: Stabilizing Dependencies

? View Code Example
// useCallback keeps 'log' stable between renders
function Example() {
  const [count, setCount] = React.useState(0);

  const log = React.useCallback(() => {
    console.log("Count is", count);
  }, [count]);

  React.useEffect(() => {
    log();
  }, [log]);
}

Wrapping log() inside useCallback prevents unnecessary effect re-runs by stabilizing its function reference, while still updating whenever count changes.

? Performance Impact

  • Correct dependencies prevent unnecessary recalculations and re-renders.
  • Too few dependencies → stale data and surprising bugs.
  • Too many dependencies → redundant computations and extra renders.
  • The goal is accuracy first, optimization second.

? Best Practices

  • ✅ Always include every value your hook depends on in the dependency array.
  • ✅ Use useCallback or useMemo to stabilize dependencies that are functions or objects.
  • ✅ For state or props that must be monitored — always add them explicitly.
  • ✅ If you intentionally want a hook to run only once, use [] — but ensure the logic is safe.
  • ✅ Use ESLint’s react-hooks/exhaustive-deps rule to automatically warn about missing dependencies.
  • ✅ Extract complex dependency logic into custom hooks to reduce confusion and duplication.

? Tips

  • Use the React Hooks ESLint plugin — it helps enforce correct dependency arrays.
  • Use useRef for values that do not need to trigger re-renders when they change.
  • Keep dependency arrays small and intentional — avoid adding unnecessary references.
  • When unsure, it’s usually safer to include a dependency than to omit it.

? Try It Yourself

  1. Create a component with useEffect that logs whenever state changes — then try adding/removing dependencies.
  2. Write a useMemo calculation that depends on two values and see how it behaves when one changes.
  3. Refactor an effect to use useCallback to stabilize function dependencies.
  4. Install the React Hooks ESLint plugin and intentionally remove a dependency — observe its warning.

Goal: Master dependency arrays so that React Hooks run efficiently, correctly, and only when their true dependencies change.