Skip to content

React never ending loop in useEffect

Apologies. React noob here.

Got an issue where my code has stuck on a never ending loop. I’ve stripped the code down only to the part I believe that’s causing the problem.

function Packages(){

    const [packages, setPackages] = useState([]);

    useEffect(() => {
        if(!packages){
            getPackages();
        }
    });

    const getPackages = async () => {
        const resp1 = await instance.get('https://jsonplaceholder.typicode.com/todos/1');
        setPackages(resp1);
    };
}

I’ve used the useEffect method to match the componentDidMount. Is that correct?

The moment I comment “setPackages(resp1);” the never ending loop stops. I probably am using the useEffect and useState hooks wrong.

Appreciate any guidance on this.

Answer

If you don’t pass a second argument to the useEffect hook, it will execute every time your component re-renders.

If you want to fetch the data only once after the component has mounted, then pass an empty dependency array to the useEffect hook.

useEffect(() => {
   if(!packages){
       getPackages();
   }
}, []);

Doing this will fix the infinite execution of useEffect hook BUT now React will throw a warning about missing dependencies of useEffect hook and that is because you will need to add the function getPackages in the dependency array of the useEffect hook BUT this will also lead to infinite execution of the useEffect hook.

You have two options to fix this problem:

  1. Get rid of getPackages function and move its code inside the useEffect hook

    useEffect(() => {
       if(!packages){
         (async () => {
           const resp1 = await instance.get(...);
           setPackages(resp1);
         })(); 
       }
    }, []);
    
  2. Use the useCallback hook to memoize the getPackages function so that it can be safely added in the dependency array of the useEffect hook.

    useEffect(() => {
         if(!packages){
             getPackages();
         }
     }, [getPackages]);
    
    const getPackages = useCallback(async () => {
        const resp1 = await instance.get(...);
        setPackages(resp1);
    }, []);
    

Also you don’t need the folowing check inside the useEffect hook:

if(!packages) {
   ...
}

This check is not only unnecessary but will also lead to problems:

  1. Using packages inside the useEffect will lead to a warning about the missing dependencies of the useEffect hook. This can be fixed by adding packages in the dependency array of the useEffect hook

  2. If you add packages in the dependency array of the useEffect hook, it can lead to your original problem, i.e. infinite execution of the useEffect hook.

    useEffect hook shouldn’t update the state that is specified in its dependency array.

  3. As the initial value of packages is an array, if (!packages) condition will never be true because an array is a truthy value and inverting it will always evaluate to false.

    You probably meant to write if (!packages.length) but as mentioned before, this check is unnecessary.

Here’s how I would rewrite your component:

function Packages() {

    const [packages, setPackages] = useState([]);

    useEffect(() => {
       instance
          .get('https://jsonplaceholder.typicode.com/todos/1')
          .then((data) => setPackages(data))
          .catch(error => { /* handle error */ };
    }, []);
}

For further reading on useEffect hook, read: Using the Effect Hook