Chapter 2 of 4

The Dependency Array

The three forms of the second argument and what each one means.

The second argument controls when the effect re-runs. React compares each dependency with its value from the previous render using Object.is.

// No array: runs after every single render
useEffect(() => { /* ... */ });

// Empty array: runs once, after the first render
useEffect(() => { /* ... */ }, []);

// With dependencies: runs when any of them changes
useEffect(() => { /* ... */ }, [userId, filter]);

Every reactive value belongs in the array

If the effect body reads a prop, a state value, or anything derived from them, it must be listed. Leaving something out gives you an effect that keeps using a stale value from an old render.

function Chat({ roomId }) {
  useEffect(() => {
    const connection = connect(roomId);
    return () => connection.close();
  }, [roomId]);   // reconnects when the room changes

  // With [] it would connect to the first room forever
}

Objects and functions as dependencies

A new object or function is created on every render, so it never equals the previous one and the effect runs every time. Move it inside the effect, or memoise it.

// Runs on every render: options is a new object each time
const options = { limit: 10 };
useEffect(() => { load(options); }, [options]);

// Fix 1: move it inside
useEffect(() => {
  load({ limit: 10 });
}, []);

// Fix 2: depend on the primitive value
useEffect(() => {
  load({ limit });
}, [limit]);

An empty array is a claim

[] says 'nothing this effect uses will ever change'. That is true for a one-off subscription to window, and usually false for anything involving props.