brendangibson
brendangibson

Reputation: 2543

Infinite loop when setting and using state in a `useCallback` that is being called from a `useEffect`

I would like to fetch data when the user changes.

To do this I have a useEffect that triggers when the user changes, which calls a function to get the data.

The problem is that the useEffect is called too often because it has a dependency on getData and getData changes because it both uses and sets loading.

Are there ways around this, while still retaining getData as a function, as I call it elsewhere.

const getData = useCallback(async () => {
    if (!loading) {
      try {
        setLoading(true);

        const { error, data } = await getDataHook();
        if (error) {
         throw new Error("blah!");
        }

      } catch (error) {
        const message = getErrorMessage(error);
        setErrorMessage(message);
        setLoading(false);
      }
    }
  }, [loading]);

...

  useEffect(() => {
    const callGetData = async () => {
      await getData();
    };
    callGetData();
  }, [user, getData]);

Upvotes: 1

Views: 500

Answers (2)

Ori Drori
Ori Drori

Reputation: 191966

The loading flag is something that the call sets, and shouldn't be effected by it, so remove it from the useEffect(), and getData() functions.

const getData = useCallback(async () => {
  try {
    setLoading(true);

    const { error, data } = await getDataHook();
    if (error) {
     throw new Error("blah!");
    }

  } catch (error) {
    const message = getErrorMessage(error);
    setErrorMessage(message);
  } finally {
    setLoading(false); // not related, but this would remove loading after an error as well
  }
}, []);

useEffect(() => {     
  const callGetData = async () => {
    await getData(user);
  };
  callGetData();
}, [user, getData]);

Upvotes: 0

p1uton
p1uton

Reputation: 266

Try moving loading from useCallback to useEffect. Something like this:

const getData = useCallback(async () => {
        try {
            const { error, data } = await getDataHook();
            if (error) {
             throw new Error("blah!");
            }

        } catch (error) {
            const message = getErrorMessage(error);
            setErrorMessage(message);
        }
  }, []);

...

  useEffect(() => {
    const callGetData = async () => {
      await getData();
    };
    if (!loading) {
      setLoading(true);
      callGetData();
      setLoading(false);
    }
  }, [user, getData, loading]);

Upvotes: 1

Related Questions