0
votes

I'm playing around with react hooks and I ran into a weird issue trying to fetch some data, when i'm creating a file to fetch data using hooks and axios

this works

import axios from 'axios';

const useResources = (resource) => {
    const [resources, setResources ] = useState([]);

    useEffect(
        () => {
            (async resource => {
                const response = await axios.get(`randomapi.com/${resource}`);
                setResources(response.data);
            })(resource);
        },
        [resource]
    );

    return resources;
};

export default useResources;

but this doesn't

import { useState, useEffect } from 'react';
import Axios from 'axios';

const fetchData = () => {
    const [data, setData] = useState('');

    useEffect( async () => {
        const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
        setData(response.data);
    });

    return data;
};

export default fetchData;

'React Hook useEffect contains a call to 'setData'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [] as a second argument to the useEffect Hook.'

Aren't they the same?

1

1 Answers

2
votes

On first glance, they are similar, but they still have differences. Let's check them:

useEffect(
        () => {
            // we create a function, that works like a "black box" for useEffect
            (async resource => { 
                const response = await axios.get(`randomapi.com/${resource}`);
                // we don't use `setResources` in dependencies array, as it's used in wrapped function
                setResources(response.data); 
            // We call this function with the definite argument
            })(resource); 

        // this callback doesn't return anything, it returns `undefined`
        },    

         // our function depends on this argument, if resource is changed, `useEffect` will be invoked
        [resource] 
    );

useEffect hook should receive a function, which can return another function to dispose of all dynamic data, listeners (e.g. remove event listeners, clear timeout callbacks, etc.)

Next example:

// this function returns a promise, but actually useEffect needs a function, 
// which will be called to dispose of resources. 
// To fix it, it's better to wrap this function 
// (how it was implemented in the first example)
useEffect( async () => { 
        const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
        setData(response.data);
// we don't set up an array with dependencies. 
// It means this `useEffect` will be called each time after the component gets rerendered. 
// To fix it, it's better to define dependencies
}); 

So, we have 2 major errors:

1) Our useEffect callback returns a Promise instead of function, which implements dispose pattern

2) We missed dependencies array. By this reason component will call useEffect callback after each update.

Let's fix them:

useEffect(() => {// we wrap async function: 
  (async () => { 
     // we define resource directly in callback, so we don't have dependencies:
     const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
     setData(response.data);
  })()  
}, []); 

Finally, we have fixed our second example :) I even made an example with custom fetchData hook and final version of useEffect: https://codesandbox.io/s/autumn-thunder-1i3ti

More about dependencies and refactoring hints for useEffect you can check here: https://github.com/facebook/react/issues/14920