Popular Searches
Popular Course Categories
Popular Courses

Effect Dependency Arrays

Effect Dependency Arrays

React Side Effects and Lifecycle

Effect Dependency Arrays in React JS

Effect Dependency Arrays are used with React's useEffect() Hook to control when an Effect needs to run again. They help React determine whether the values used by an Effect have changed since the previous render.

Dependency arrays are useful when working with API calls, timers, event listeners, subscriptions, browser APIs, and other external systems. Understanding them helps prevent unnecessary Effect executions, stale values, and infinite rendering loops.

Course Link: JustAcademy React JS Training Course

Demo Link: Register for React JS Course Demo


1. What Is a Dependency Array?

A dependency array is an array passed as the second argument to useEffect(). It contains the reactive values that the Effect reads and depends on, such as props, state, and variables or functions declared inside the component.

React compares each dependency with its previous value using Object.is. If at least one dependency changes, React runs the previous cleanup function, if provided, and then runs the Effect's new setup function after the update is committed.

Syntax

useEffect(() => {
  // Effect logic
  return () => {
    // Optional cleanup logic
  };
}, [dependency1, dependency2]);

Explanation:

  • useEffect() declares an Effect.
  • The setup function contains the synchronization logic.
  • The cleanup function optionally stops or reverses the setup work.
  • The dependency array specifies the reactive values that determine when the Effect must run again.

2. Types of useEffect Dependency Arrays

There are three common ways to use useEffect(), depending on whether a dependency array is provided and what it contains.

2.1 Without a Dependency Array

If the dependency array is omitted, the Effect runs after every committed render.

import { useEffect, useState } from 'react';
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("Effect executed");
  });
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}
export default Counter;

Execution: The Effect runs after the initial commit and after each subsequent committed render. Clicking the button updates the state, causing another render and another Effect execution.

2.2 With an Empty Dependency Array

An empty dependency array tells React that the Effect has no reactive dependencies.

import { useEffect, useState } from 'react';
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("Effect setup");
  }, []);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}
export default Counter;

Execution: The Effect runs after the initial commit and does not run again just because the component's state or props change.

Important: In development, React Strict Mode may run an extra setup-and-cleanup cycle to help identify missing cleanup logic. Therefore, the setup function may execute more than once during development.

2.3 With Dependencies

A dependency array containing values tells React to re-run the Effect when one or more of those values change.

import { useEffect, useState } from 'react';
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("Current count:", count);
  }, [count]);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}
export default Counter;

Execution: The Effect runs after the initial commit and again when count changes. If another state value changes but count stays the same, this Effect does not need to run again.

Comparison Table

Syntax Execution Example use
useEffect(() => {}) After every committed render Synchronization that genuinely needs to occur after every commit
useEffect(() => {}, []) After initial commit; development Strict Mode may repeat setup for testing Setting up a subscription with cleanup
useEffect(() => {}, [count]) After initial commit and when count changes Synchronization based on a count value
useEffect(() => {}, [userId, page]) After initial commit and when either dependency changes Loading data for a particular user and page

3. How React Checks Dependencies

React compares each dependency in the current array with the corresponding dependency from the previous render using Object.is.

If all dependencies are unchanged, React skips re-running that Effect. If any dependency changes, React runs the old cleanup function first, if one exists, and then runs the new setup function.

Example

useEffect(() => {
  console.log("Selected category:", category);
}, [category]);

If category changes from "Books" to "Electronics", the Effect runs again after the update. If the value remains "Books", the Effect does not re-run solely because of this dependency.

Example with Multiple Dependencies

useEffect(() => {
  console.log("User:", userId);
  console.log("Page:", page);
}, [userId, page]);

The Effect runs again if either userId or page changes. If both values remain unchanged, the Effect is skipped after subsequent commits.


4. What Are Reactive Dependencies?

Reactive dependencies are values that can change between renders and are read by the Effect. These commonly include the following:

  • Props: Values passed from a parent component.
  • State: Values managed using Hooks such as useState().
  • Component variables: Variables declared inside the component body.
  • Component functions: Functions declared inside the component body and referenced by the Effect.

Example

function Profile({ userId }) {
  const [status, setStatus] = useState("active");
  useEffect(() => {
    console.log(userId, status);
  }, [userId, status]);
  return <p>User status: {status}</p>;
}

Explanation: The Effect reads both userId and status, so both reactive values must be included in the dependency array.


5. Why Should You Include All Dependencies?

Every reactive value used by the Effect should be represented in its dependency array. Omitting a dependency may cause the Effect to keep using a value from an earlier render, resulting in stale data or incorrect behavior.

Incorrect Example

function Profile({ userId }) {
  useEffect(() => {
    console.log("User ID:", userId);
  }, []);
  return <p>Profile</p>;
}

Problem: The Effect reads userId but does not list it as a dependency. If userId changes, the Effect will not re-run to reflect that change.

Correct Example

function Profile({ userId }) {
  useEffect(() => {
    console.log("User ID:", userId);
  }, [userId]);
  return <p>Profile</p>;
}

Result: The Effect runs after the initial commit and re-runs when userId changes.

Best practice: Use the React Hooks dependency linter to identify missing dependencies. Do not remove dependencies merely to stop an Effect from re-running. Instead, adjust the code so that the Effect only depends on the values it genuinely needs.


6. Dependency Arrays with API Calls

Dependency arrays are useful when fetching data based on a changing value, such as a user ID or product ID.

Example: Fetch User Details

import { useEffect, useState } from 'react';
function UserDetails({ userId }) {
  const [user, setUser] = useState(null);
  const [error, setError] = useState("");
  useEffect(() => {
    const controller = new AbortController();
    async function fetchUser() {
      try {
        setError("");
        setUser(null);
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users/${userId}`,
          { signal: controller.signal }
        );
        if (!response.ok) {
          throw new Error("Unable to fetch user");
        }
        const data = await response.json();
        setUser(data);
      } catch (err) {
        if (err.name !== "AbortError") {
          setError(err.message);
        }
      }
    }
    fetchUser();
    return () => controller.abort();
  }, [userId]);
  if (error) return <p>Error: {error}</p>;
  if (!user) return <p>Loading user...</p>;
  return <h2>{user.name}</h2>;
}
export default UserDetails;

Explanation:

  1. The component receives a userId prop.
  2. The Effect runs after the initial commit and requests that user's data.
  3. If userId changes, React runs the previous cleanup and aborts the old request before the next Effect setup.
  4. The new Effect fetches the details for the new user ID.

This is a learning example. For larger applications, framework data-loading features or a dedicated data-fetching library may provide better caching, deduplication, and error handling.


7. Dependency Arrays with Timers

A timer can be set up inside an Effect. The dependency array determines when the timer setup needs to be repeated.

Example: Timer That Increments Every Second

import { useEffect, useState } from 'react';
function Timer() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const timerId = setInterval(() => {
      setSeconds(previous => previous + 1);
    }, 1000);
    return () => clearInterval(timerId);
  }, []);
  return <h2>Seconds: {seconds}</h2>;
}
export default Timer;

Explanation: The empty dependency array prevents the interval from being recreated after each count update. The functional state updater previous => previous + 1 uses the previous state without requiring seconds in the dependency array. The cleanup clears the timer when needed.


8. Objects and Functions as Dependencies

Objects and functions created inside a component usually receive a new identity on each render. Because React compares dependencies by identity using Object.is, this can cause an Effect to re-run more frequently than expected.

Problem: Object Created During Rendering

function ChatRoom({ roomId }) {
  const options = {
    room: roomId,
    server: "chat.example.com"
  };
  useEffect(() => {
    console.log("Connecting:", options);
  }, [options]);
  return <p>Room: {roomId}</p>;
}

Problem: The options object is recreated during each render, so its identity changes and may trigger the Effect again.

Improved Example

function ChatRoom({ roomId }) {
  useEffect(() => {
    const options = {
      room: roomId,
      server: "chat.example.com"
    };
    console.log("Connecting:", options);
  }, [roomId]);
  return <p>Room: {roomId}</p>;
}

Explanation: Creating the object inside the Effect means the Effect only needs to depend on roomId. This is often simpler than memoizing the object.

The same principle applies to functions created during rendering. When practical, define Effect-specific helper functions inside the Effect. Use useMemo() or useCallback() only when there is a genuine need to preserve identity.


9. Dependency Arrays and Infinite Loops

An infinite Effect loop can occur when an Effect updates state, that update causes a render, and the render changes one or more dependencies so that the Effect runs again.

Incorrect Example

function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount(count + 1);
  }, [count]);
  return <p>Count: {count}</p>;
}

Problem: Every Effect execution increments count. That changes the dependency, causing another Effect execution and another update.

Solution: First ask whether the Effect is necessary. If the value can be derived during rendering, calculate it directly. If an external system must be synchronized, update state only when required and ensure dependencies accurately represent the synchronization.


10. Dependency Arrays and React Strict Mode

React Strict Mode performs extra development-only checks to help detect bugs. When Strict Mode is enabled, React runs an additional setup-and-cleanup cycle for Effects during initial mounting.

useEffect(() => {
  console.log("Effect setup");
  return () => {
    console.log("Effect cleanup");
  };
}, []);

In development with Strict Mode, the console may show:

Effect setup
Effect cleanup
Effect setup

This extra cycle does not mean that the empty dependency array is being ignored. It is a development check. Make sure the cleanup reverses the setup instead of trying to prevent the extra cycle with a ref.


11. Common Mistakes with Dependency Arrays

Mistake Why it is a problem Better approach
Leaving out a reactive value The Effect may use an outdated value. Include all reactive values used by the Effect.
Using no dependency array unintentionally The Effect runs after every committed render. Specify the correct dependency array when appropriate.
Using an empty array despite reading changing props or state The Effect does not re-synchronize when those values change. Include the reactive values or restructure the Effect.
Depending on a newly created object The object's identity changes on each render. Create the object inside the Effect where possible.
Depending on an unnecessary function A new function identity can cause repeated execution. Move the helper inside the Effect when appropriate.
Suppressing dependency warnings Missing dependencies can create hard-to-find bugs. Follow the linter and restructure the code instead.
Forgetting cleanup Timers, listeners, or subscriptions may remain active. Return a cleanup function when the setup requires it.

12. Best Practices for Dependency Arrays

  • Always declare every reactive value used by the Effect.
  • Keep the dependency array inline and use a constant number of dependencies.
  • Use an empty array only when the Effect genuinely has no reactive dependencies.
  • Use cleanup for timers, subscriptions, event listeners, and other resources that must be stopped.
  • Do not use an Effect for calculations that can be performed during rendering.
  • Prefer functional state updates when updating state based on its previous value.
  • Move Effect-specific objects and helper functions inside the Effect when doing so simplifies dependencies.
  • Use the React Hooks linter to identify missing dependencies and common mistakes.
  • Test Effects with Strict Mode enabled during development.

13. Real-World Uses of Dependency Arrays

  • Search filters: Re-fetch or synchronize search results when a query changes.
  • User profiles: Load details when the selected user ID changes.
  • Product pages: Synchronize product data when a product ID changes.
  • Chat applications: Connect to a different chat room when the room ID changes.
  • Timers: Set up or restart timers when their configuration changes.
  • Event listeners: Re-register listeners when the values they use need to change.
  • Third-party integrations: Synchronize charts, maps, or other external widgets with React state.

14. Interview Questions and Answers

Q1. What is a dependency array in React?

Answer: It is the second argument to useEffect() and lists the reactive values that determine when the Effect needs to run again.

Q2. What happens when no dependency array is provided?

Answer: The Effect runs after every committed render.

Q3. What does an empty dependency array mean?

Answer: The Effect has no reactive dependencies and does not re-run merely because the component's props or state change. Strict Mode may run an extra setup-and-cleanup cycle in development.

Q4. How does React compare dependencies?

Answer: React compares each dependency with its previous value using Object.is.

Q5. Why should all reactive dependencies be included?

Answer: To ensure the Effect responds to changes in the values it reads and avoids using stale data.

Q6. Can an Effect have multiple dependencies?

Answer: Yes. For example, [userId, page] makes the Effect respond when either value changes.

Q7. Why can objects cause unnecessary Effect executions?

Answer: Objects created during rendering generally have new identities, and React compares dependencies by identity.

Q8. What is the purpose of a cleanup function?

Answer: It reverses or stops the work started by an Effect, such as clearing a timer or disconnecting a subscription.

Q9. How can you avoid an infinite Effect loop?

Answer: Check whether the Effect is needed, include the correct dependencies, avoid unnecessary state updates, and ensure that state changes do not continuously change the dependencies.

Q10. Should you remove a dependency just to prevent re-execution?

Answer: No. Restructure the Effect so that it no longer needs that dependency, or include the dependency if it is genuinely reactive.


15. Practice Exercises

  1. Create a counter whose Effect logs the current count whenever it changes.
  2. Create a component that logs a message after every committed render.
  3. Create a component with an empty dependency array and observe its behavior in Strict Mode.
  4. Fetch user details whenever a userId prop changes.
  5. Create a timer that increments every second and cleans up its interval.
  6. Create a chat-room example that re-synchronizes when roomId changes.
  7. Fix an Effect that runs repeatedly because it depends on an object created during rendering.

16. Quick Revision Checklist

  • Understand the three dependency patterns: omitted, empty, and populated arrays.
  • Know that React compares dependencies using Object.is.
  • Include all reactive values used by an Effect.
  • Understand setup, cleanup, and re-execution when dependencies change.
  • Recognize stale values and infinite loops.
  • Know why newly created objects and functions can cause repeated executions.
  • Understand the extra Effect setup-and-cleanup cycle in development Strict Mode.

17. Conclusion

Effect dependency arrays help React determine when an Effect needs to synchronize again. An omitted array causes execution after every committed render, an empty array indicates no reactive dependencies, and a populated array allows React to respond to changes in specified values.

Correct dependency management improves application reliability, avoids stale values, and reduces unnecessary work. Always include the reactive values used by an Effect and provide cleanup when external resources need to be released.

Official React Documentation: useEffect Reference | Removing Effect Dependencies | Lifecycle of Reactive Effects

Learn React JS with JustAcademy: Explore React JS Training | Register for a Course Demo

whatsapp