0

When I try to run code like below, I get the eslint warning React Hook useEffect has a missing dependency: 'userApi'. Either include it or remove the dependency array.(react-hooks/exhaustive-deps)

const userApi = useFetch();

useEffect(() => {
  userApi.run("/api/user");
}, []);

But if I add userApi as dependency, then I get a recursive loop. If I ignore the warning everything is fine. Should I just ignore it?

Here is my useFetch hook:

const useFetch = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const auth = useAuth();

  const run = async (url, method = "GET", data, options) => {
    setError();
    setIsLoading(true);
    try {
      const token = await auth.user.getIdToken();

      if (!options)
        options = {
          method,
          headers: {
            "Content-Type": "application/json"
          }
        };

      if (!options.headers) options.headers = {};
      if (!options.headers["Authorization"])
        options.headers["Authorization"] = `Bearer ${token}`;

      if (!options.body && data) options.body = JSON.stringify(data);

      const response = await fetch(url, options);
      console.log(response);
      if (!response.ok) throw Error(response.statusText);
      const json = await response.json();
      setData(json);
    } catch (e) {
      console.log(e);
      setError(e.message);
    }
    setIsLoading(false);
  };

  return { data, error, isLoading, run };
};

Update, my working code:

const [getUser, { data: user, error, loading }] = useFetch("/api/user");

useEffect(() => {
  getUser();
}, [getUser]);

The fetch hook:

const useFetch = (url, method = "GET") => {
    const [loading, setLoading] = useState(false);
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);
    const { user } = useAuth();

    const run = useCallback(
        async (data, options) => {
          ...code...
        },
        [user, method, url]
    );
    return [run, { data, error, loading }];
};

1 Answer 1

2

You can fix the problem by updating the useFetch code (and maybe useAuth also) by adding some memoization using useMemo and useCallback, something like this :

const useFetch = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const auth = useAuth(); // probably should update useAuth also

  // preserve the `run` reference between renders
  const run = useCallback(async (url, method = "GET", data, options) => {
    // your code ...
  }, [auth.user]);

  // preserve the object reference between renders
  return useMemo(
    () => ({ data, error, isLoading, run }), 
    [data, error, isLoading, run]
  );
};

And

  // Use the function `run` directly in case the object from `useFetch` has changed (e.g when data change)
  const { run } = useFetch(); 

  useEffect(() => {
    run("/api/user");
  }, [run]);
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, that works. The only downside is that userApi.run was nicer to use and read for me. (Sometimes I have multiple useFetch hooks in a single component)
I can still use userApi.run instead of run with userApi.run as dependency, but then eslint checker still complains about it. I think the linter is not smart enough in this case.
You can alias it when destructuring the object : const { run: userApiRun } = useFetch();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.