Solve - React Hook useEffect has a missing dependency error

Solve the warning React Hook useEffect has a missing dependency error. Either include it or remove the dependency array.

The warning “React Hook useEffect has a missing dependency” occurs when the useEffect hook makes use of a variable or function outside its scope that we haven’t included inside its dependency array.

React Hook useEffect has a missing dependency error

In this article, I will list two different scenarios where the missing dependency error can be generated and also proffer solutions to how the error can be fixed.

we’ll start with a simple Counter example and then walk our way to data fetching with useEffect Hook.

Example One with Counter

In the snippets below, the count variable is been used in the useEffect hook but we didn’t include it in the dependencies array.


import { useEffect, useState } from 'react';

const TestPage = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const countId = setInterval(() => {
      setCount(count + 1);
    }, 3000);

    return () => clearInterval(countId);

    // ? React Hook useEffect has a missing dependency: 'count'. 
    // Either include it or remove the dependency array.
  }, []);

  return (
    <div>
      <p>You clicked: {count} times </p>
      <button
        style={{ marginRight: 10 }}
        onClick={() => setCount((prev) => prev + 1)}
      >
        Click me
      </button>
    </div>
  );
};

export default TestPage;


There are different ways we can solve this error but am going to list three obvious solutions.

Three Ways to Solve useEffect missing dependency error

Note: Don’t lie to React about the dependencies you used in the useEffect hook. Doing that will lead to some consequences, you might experience an infinite data re-fetching loop, your component might not work correctly or a socket connection will be recreated frequently.

Solution One: Be honest to React and provide the missing dependencies

In this solution, we are not lying to React but it might not be the best solution. Before we start adding variables or functions inside the dependencies array we first need to ask ourselves what purpose is that variable or function serving in the useEffect hook.

As you can see we are only using it inside the setCount call. In this scenario, we don’t even need the count variable in the useEffect scope at all. Since we are using the count variable as the previous state to update the new state, we can use the functional updater form of setCount.


import { useEffect, useState } from 'react';

const TestPage = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const countId = setInterval(() => {
      setCount(count + 1);
    }, 3000);

    return () => clearInterval(countId);

    // ? Now React is happy
  }, [count]);

  return (
    <div>
      <p>You clicked: {count} times </p>
      <button
        style={{ marginRight: 10 }}
        onClick={() => setCount((prev) => prev + 1)}
      >
        Click me
      </button>
    </div>
  );
};

export default TestPage;

Solution Two: Passing an updater callback to the setCount function

In the snippets below, since the new state is computed with the previous state I removed the count variable from the useEffect hook and passed a callback function to setCount . The callback function (prev) => prev + 1 receives the previous value and returns the updated value.

With this solution, we can safely remove the count variable from the dependencies array.


import { useEffect, useState } from 'react';

const TestPage = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const countId = setInterval(() => {
      // ? Updater form
      setCount((prev) => prev + 1);
    }, 3000);

    return () => clearInterval(countId);
    // ? React is Happy
  }, []);

  return (
    <div>
      <p>You clicked: {count} times </p>
      <button
        style={{ marginRight: 10 }}
        onClick={() => setCount((prev) => prev + 1)}
      >
        Click me
      </button>
    </div>
  );
};

export default TestPage;


Solution Three: Lie to React and disable the Eslint warning

This last solution gets the job done but the counter component will not work correctly since we lied to React that the effect doesn’t depend on any variable by passing an empty dependencies array.

When you run this code in the first render count is 0. Therefore when the useEffect was evoked setCount(count + 1) will be setCount(0+1) .

Since we lied to React and provided an empty array as the dependencies array, the useEffect hook won’t be called. Since we didn’t re-run the effect setCount(0 + 1) will be called every 3 seconds.


import { useEffect, useState } from 'react';

const TestPage = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const countId = setInterval(() => {
      setCount(count + 1);
    }, 3000);

    return () => clearInterval(countId);

    // ? Only do this when necessary

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div>
      <p>You clicked: {count} times </p>
      <button
        style={{ marginRight: 10 }}
        onClick={() => setCount((prev) => prev + 1)}
      >
        Click me
      </button>
    </div>
  );
};

export default TestPage;


Example Two: Data fetching with useEffect Hook

In this example, we are fetching a specific user from the JsonPlaceHolder API based on the user’s id.

I created two states, one stores the user info, and the other store the query which is the user’s id.

My goal is to fetch a new user whenever the query is incremented by 1 but it doesn’t work as expected since am lying to React by passing an empty dependencies array.


import { useEffect, useState } from 'react';

const TestPage = () => {
  const [query, setQuery] = useState(1);
  const [user, setUser] = useState({});

  const fetchUser = async () => {
    const res = fetch(
      `https://jsonplaceholder.typicode.com/users/${query}`
    ).then((res) => res.json());
    const user = await res;
    setUser(user);
  };

  useEffect(() => {
    fetchUser();
    // ? React Hook useEffect has a missing dependency: 'fetchUser'.
    // Either include it or remove the dependency array.
  }, []);

  if (!user) {
    return <p>loading...</p>;
  }

  return (
    <div>
      <h1>User</h1>
      <p>Id: {user.id}</p>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <div>
        <button onClick={() => setQuery((prev) => prev + 1)}>
          Change User
        </button>
      </div>
    </div>
  );
};

export default TestPage;


When you check the useEffect dependency error, it says I should include the fetchUser function in the dependencies array.

In React whenever a component re-renders all the functions within it are re-created since functions are objects or reference types.

So putting the fetchUser function in the dependencies array will cause the component to re-render in an infinite loop.

Let’s try and add the fetchUser function to the dependencies array to see whether React will yell at us.

the function makes dependencies of useEffect hook to change on every render

From the screenshot, you can see React is not happy at all. It has provided us with two options.

  1. Move the function (fetchUser) into the useEffect hook
  2. Define the function (fetchUser) in a useCallback hook

In our case, you can see the only place we are using the fetchUser function is in the useEffect hook so it makes a lot of sense to move the function inside the useEffect hook.

Now, let’s move the fetchUser function into the useEffect hook and see if React will complain again.


import { useEffect, useState } from 'react';

const TestPage = () => {
  const [query, setQuery] = useState(1);
  const [user, setUser] = useState({});

  
  useEffect(() => {
    const fetchUser = async () => {
      const res = fetch(
        `https://jsonplaceholder.typicode.com/users/${query}`
      ).then((res) => res.json());
      const user = await res;
      setUser(user);
    };

    fetchUser();
    // ? React Hook useEffect has a missing dependency: 'query'. 
    // Either include it or remove the dependency array
  }, []);

  if (!user) {
    return <p>loading...</p>;
  }

  return (
    <div>
      <h1>User</h1>
      <p>Id: {user.id}</p>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <div>
        <button onClick={() => setQuery((prev) => prev + 1)}>
          Change User
        </button>
      </div>
    </div>
  );
};

export default TestPage;


From the code snippets above you can see React recognizes that the useEffect is now dependent on the query variable so it’s warning us to include it.

Let’s include the query variable in the dependencies array.


useEffect(() => {
    const fetchUser = async () => {
      const res = fetch(
        `https://jsonplaceholder.typicode.com/users/${query}`
      ).then((res) => res.json());
      const user = await res;
      setUser(user);
    };

    fetchUser();
    // ? Everything is now working correctly
  }, [query]);

Waooo… the component now works as expected.

In some cases, you might not want to move a function inside useEffect. For example, several effects in the same component may call the same function, and you don’t want to duplicate its logic.

This is when the useCallback hook becomes useful. We can now put the fetchUser function inside the useCallback hook and provide it with the query variable as a dependency.


import { useCallback, useEffect, useState } from 'react';

const fetchUser = useCallback(async () => {
    const res = fetch(
      `https://jsonplaceholder.typicode.com/users/${query}`
    ).then((res) => res.json());
    const user = await res;
    setUser(user);
    // ? provide it with the query variable
  }, [query]);

  useEffect(() => {
    fetchUser();
    // ? Safely include the fetchUser function in the dependencies array
  }, [fetchUser]);

  if (!user) {
    return <p>loading...</p>;
  }

  return (
    <div>
      <h1>User</h1>
      <p>Id: {user.id}</p>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <div>
        <button onClick={() => setQuery((prev) => prev + 1)}>
          Change User
        </button>
      </div>
    </div>
  );
};
  
export default TestPage;


The useCallback hook adds another layer of dependency. This ensures that the function will only change when the dependencies change.

The fetchUser function will re-run whenever the query variable changes. When the query changes, the fetchUser function will also change and re-run to fetch the user.

In conclusion, the fetchUser function will only re-run when necessary so we can safely include it in the useEffect dependencies array.