[javascript] React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing

I was trying the useEffect example something like below:

useEffect(async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}, []);

and I get this warning in my console. But the cleanup is optional for async calls I think. I am not sure why I get this warning. Linking sandbox for examples. https://codesandbox.io/s/24rj871r0p enter image description here

This question is related to javascript reactjs react-hooks

The answer is


I read through this question, and feel the best way to implement useEffect is not mentioned in the answers. Let's say you have a network call, and would like to do something once you have the response. For the sake of simplicity, let's store the network response in a state variable. One might want to use action/reducer to update the store with the network response.

const [data, setData] = useState(null);

/* This would be called on initial page load */
useEffect(()=>{
    fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then(data => {
        setData(data);
    })
    .catch(err => {
        /* perform error handling if desired */
    });
}, [])

/* This would be called when store/state data is updated */
useEffect(()=>{
    if (data) {
        setPosts(data.children.map(it => {
            /* do what you want */
        }));
    }
}, [data]);

Reference => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects


When you use an async function like

async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}

it returns a promise and useEffect doesn't expect the callback function to return Promise, rather it expects that nothing is returned or a function is returned.

As a workaround for the warning you can use a self invoking async function.

useEffect(() => {
    (async function() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    })();
}, []);

or to make it more cleaner you could define a function and then call it

useEffect(() => {
    async function fetchData() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    };
    fetchData();
}, []);

the second solution will make it easier to read and will help you write code to cancel previous requests if a new one is fired or save the latest request response in state

Working codesandbox


Until React provides a better way, you can create a helper, useEffectAsync.js:

import { useEffect } from 'react';


export default function useEffectAsync(effect, inputs) {
    useEffect(() => {
        effect();
    }, inputs);
}

Now you can pass an async function:

useEffectAsync(async () => {
    const items = await fetchSomeItems();
    console.log(items);
}, []);

Update

If you choose this approach, note that it's bad form. I resort to this when I know it's safe, but it's always bad form and haphazard.

Suspense for Data Fetching, which is still experimental, will solve some of the cases.

In other cases, you can model the async results as events so that you can add or remove a listener based on the component life cycle.

Or you can model the async results as an Observable so that you can subscribe and unsubscribe based on the component life cycle.


void operator could be used here.
Instead of:

React.useEffect(() => {
    async function fetchData() {
    }
    fetchData();
}, []);

or

React.useEffect(() => {
    (async function fetchData() {
    })()
}, []);

you could write:

React.useEffect(() => {
    void async function fetchData() {
    }();
}, []);

It is a little bit cleaner and prettier.


Async effects could cause memory leaks so it is important to perform cleanup on component unmount. In case of fetch this could look like this:

function App() {
    const [ data, setData ] = React.useState([]);

    React.useEffect(() => {
        const abortController = new AbortController();
        void async function fetchData() {
            try {
                const url = 'https://jsonplaceholder.typicode.com/todos/1';
                const response = await fetch(url, { signal: abortController.signal });
                setData(await response.json());
            } catch (error) {
                console.log('error', error);
            }
        }();
        return () => {
            abortController.abort(); // cancel pending fetch request on component unmount
        };
    }, []);

    return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

Please try this

 useEffect(() => {
        (async () => {
          const products = await api.index()
          setFilteredProducts(products)
          setProducts(products)
        })()
      }, [])

For other readers, the error can come from the fact that there is no brackets wrapping the async function:

Considering the async function initData

  async function initData() {
  }

This code will lead to your error:

  useEffect(() => initData(), []);

But this one, won't:

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

(Notice the brackets around initData()


try

_x000D_
_x000D_
const MyFunctionnalComponent: React.FC = props => {_x000D_
  useEffect(() => {_x000D_
    // Using an IIFE_x000D_
    (async function anyNameFunction() {_x000D_
      await loadContent();_x000D_
    })();_x000D_
  }, []);_x000D_
  return <div></div>;_x000D_
};
_x000D_
_x000D_
_x000D_


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to reactjs

Error: Node Sass version 5.0.0 is incompatible with ^4.0.0 TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Template not provided using create-react-app How to resolve the error on 'react-native start' Element implicitly has an 'any' type because expression of type 'string' can't be used to index Invalid hook call. Hooks can only be called inside of the body of a function component How to style components using makeStyles and still have lifecycle methods in Material UI? React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? Unable to load script.Make sure you are either running a Metro server or that your bundle 'index.android.bundle' is packaged correctly for release

Examples related to react-hooks

Invalid hook call. Hooks can only be called inside of the body of a function component React Hook "useState" is called in function "app" which is neither a React function component or a custom React Hook function How to fix missing dependency warning when using useEffect React Hook? react hooks useEffect() cleanup for only componentWillUnmount? How to use callback with useState hook in react Push method in React Hooks (useState)? React Hooks useState() with Object useState set method not reflecting change immediately React hooks useState Array Can I set state inside a useEffect hook