react hook warnings for async function in useeffect: useeffect function must return a cleanup function or nothing

  • Last Update :
  • Techknowledgy :

The async function will return a JS promise with an implicit undefined value. This is not the expectation of useEffect. 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. I strongly recommend that you do not define your query inside the useEffect Hook, because it will be re-render infinite times. And since you cannot make the useEffect async, you can make the function inside of it to be async. and no effects. But in the meantime you can move the async stuff to a separate function and call it.

This is our Splunktool team suggestion ✌, we tried and its working fine
  useEffect(() => {
     //your code goes here
     return () => {
        //your cleanup code codes here
     };
  }, []);
function Example() {
  const [data, dataSet] = useState<any>(null)

  useEffect(() => {
    async function fetchMyAPI() {
      let response = await fetch('api/data')
      response = await response.json()
      dataSet(response)
    }

    fetchMyAPI()
  }, [])

  return <div>{JSON.stringify(data)}</div>
}
3._
const response = MyAPIResource.read();

If you use useCallback, look at example of how it works useCallback. Sandbox.

import React, { useState, useEffect, useCallback } from "react";

export default function App() {
  const [counter, setCounter] = useState(1);

  // if counter is changed, than fn will be updated with new counter value
  const fn = useCallback(() => {
    setCounter(counter + 1);
  }, [counter]);

  // if counter is changed, than fn will not be updated and counter will be always 1 inside fn
  /*const fnBad = useCallback(() => {
      setCounter(counter + 1);
    }, []);*/

  // if fn or counter is changed, than useEffect will rerun
  useEffect(() => {
    if (!(counter % 2)) return; // this will stop the loop if counter is not even

    fn();
  }, [fn, counter]);

  // this will be infinite loop because fn is always changing with new counter value
  /*useEffect(() => {
    fn();
  }, [fn]);*/

  return (
    <div>
      <div>Counter is {counter}</div>
      <button onClick={fn}>add +1 count</button>
    </div>
  );
}

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);
   }
}

Suggestion : 2

useEffect function must return a cleanup function or nothing Here, we are passing async function to the useEffect hook. As you may be aware, async functions return a Promise. However, useEffect expects the function to either return nothing or a clean up function. Hence reacts throws this warning. Now the function passed to useEffect returns nothing, thus by fulfilling the condition. Effect callbacks are synchronous to prevent race conditions. Put the async function inside:

1._
import { useEffect, useState } from "react"

function App() {
  const [posts, setPosts] = useState([])

  useEffect(async () => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts`)
      const data = await response.json()
      setPosts(data)
    } catch (e) {
      console.error(e)
    }
  }, [])

  return (
    <div className="App">
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  )
}

export default App
2._
import { useEffect, useState } from "react"

function App() {
  const [posts, setPosts] = useState([])

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/posts`
        )
        const data = await response.json()
        setPosts(data)
      } catch (e) {
        console.error(e)
      }
    }
    fetchData()
  }, [])
  return (
    <div className="App">
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  )
}

export default App
3._
import { useEffect, useState } from "react"

function App() {
  const [posts, setPosts] = useState([])

  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/posts`)
      .then(response => response.json())
      .then(data => {
        setPosts(data)
      })
      .catch(e => {
        console.log(e)
      })
  }, [])
  return (
    <div className="App">
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  )
}

export default App

Suggestion : 3

React’s useEffect cleanup function saves applications from unwanted behaviors like memory leaks by cleaning up effects. In doing so, we can optimize our application’s performance. As stated previously, the useEffect cleanup function helps developers clean effects that prevent unwanted behaviors and optimizes application performance. The useEffect Hook is built in a way that we can return a function inside it and this return function is where the cleanup happens. The cleanup function prevents memory leaks and removes some unnecessary and unwanted behaviors.

Note that you don’t update the state inside the return function either:

useEffect(() => {
   effect
   return () => {
      cleanup
   }
}, [input])

However, it is pertinent to note that the useEffect cleanup function does not only run when our component wants to unmount, it also runs right before the execution of the next scheduled effect.

In fact, after our effect executes, the next scheduled effect is usually based on the dependency(array):

// The dependency is an array
useEffect(callback, dependency)

To begin cleaning up a subscription, we must first unsubscribe because we don’t want to expose our app to memory leaks and we want to optimize our app.

To unsubscribe from our subscriptions before our component unmounts, let’s set our variable, isApiSubscribed, to true and then we can set it to false when we want to unmount:

useEffect(() => {
   // set our variable to true
   let isApiSubscribed = true;
   axios.get(API).then((response) => {
      if (isApiSubscribed) {
         // handle success
      }
   });
   return () => {
      // cancel the subscription
      isApiSubscribed = false;
   };
}, []);

We can go further and add an error condition in our catch so our fetch request won’t throw errors when we abort. This error happens because, while unmounting, we still try to update the state when we handle our errors.

What we can do is write a condition and know what kind of error we will get; if we get an abort error, then we don’t want to update the state:

useEffect(() => {
   const controller = new AbortController();
   const signal = controller.signal;

   fetch(API, {
         signal: signal
      })
      .then((response) => response.json())
      .then((response) => {
         // handle success
         console.log(response);
      })
      .catch((err) => {
         if (err.name === 'AbortError') {
            console.log('successfully aborted');
         } else {
            // handle error
         }
      });
   return () => {
      // cancel the request before component unmounts
      controller.abort();
   };
}, []);

So, let’s see how we can do the same using the Axios’ cancellation option, the Axios cancel token,

We first store the CancelToken.source() from Axios in a constant named source, pass the token as an Axios option, and then cancel the request anytime with source.cancel():

useEffect(() => {
   const CancelToken = axios.CancelToken;
   const source = CancelToken.source();
   axios
      .get(API, {
         cancelToken: source.token
      })
      .catch((err) => {
         if (axios.isCancel(err)) {
            console.log('successfully aborted');
         } else {
            // handle error
         }
      });
   return () => {
      // cancel the request before component unmounts
      source.cancel();
   };
}, []);

Suggestion : 4

This WORKS, but you should avoid it. Why? Because React’s useEffect hook expects a cleanup function returned from it which is called when the component unmounts. Using an async function here will cause a bug as the cleanup function will never get called. Yikes! So what do we do? Either way, we’re now safe to use async functions inside useEffect hooks. Now if/when you want to return a cleanup function, it will get called and we also keep useEffect nice and clean and free from race conditions. In this post you’ll learn how to use an async function inside your React useEffect hook.

1._
const Users = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetchUsers().then((users) => setUsers(users));
  }, []);

  if (!users) return <div>Loading...</div>;

  return (
    <ul>
      {users.map((user) => (
        <li>{user.name}</li>
      ))}
    </ul>
  );
};
2._
// ❌ Don't do this!
useEffect(async () => {
   const users = await fetchUsers();
   setUsers(users);

   return () => {
      // this never gets called, hello memory leaks...
   };
}, []);
3._
// 🆗 Ship it
useEffect(() => {
   (async () => {
      const users = await fetchUsers();
      setUsers(users);
   })();

   return () => {
      // this now gets called when the component unmounts
   };
}, []);

Suggestion : 5

Here you can see the basic setup for the component described above. When the page loads, the component should load the users from an API and once that is finished it will update its state accordingly. Looks great, but this will trigger the useEffect must not return anything besides a function, which is used for clean-up warning mentioned above. That is because the callback itself is returning a promise which is incompatible with useEffect. As such, this hook is frequently used for asynchronous requests like loading data for a component as soon as that component is rendered or loading new data based off another value changing. Despite its many uses, creating this sort of functionality can be tricky and you may end up running into a warning like useEffect must not return anything besides a function, which is used for clean-up. Continuing our series about React, specifically React hooks, let’s take a look at the useEffect hook. This is a very powerful hook that allows you to handle side effects of state or prop changes as well as creating on-mount functionality similar to componentDidMount in class components.

As mentioned previously, wanting to do something asynchronously inside of useEffect is a common use-case for this hook. For example, imagine you have a component that shows a list of users but those users come from a database so you must request them on page load. In its simplest form, you might have a component like this:

const UsersComponent = () => {
  const [users, setUsers] = useState([]);
  const [usersLoading, setUsersLoading] = useState(false);

  useEffect(async () => {
    setUsersLoading(true);
    // getUsers is your api call function that returns a promise
    const result = await getUsers();
    setUsers(result);
    setUsersLoading(false);
    // no values in the dependency array means this effect will run once when the component mounts
  }, []);

  return (
    <div>
      <h1>Users</h1>
      <div>
        {users.map((user) => (
          <div key={user.id}>{user.username}</div>
        ))}
      </div>
    </div>
  );
};Code language: JavaScript (javascript)

The fix here is actually quite simple. You should wrap your asynchronous work in a function and then just call that from the useEffect callback. Here is that solution using the example above:

const UsersComponent = () => {
  const [users, setUsers] = useState([]);
  const [usersLoading, setUsersLoading] = useState(false);

  useEffect(() => {
    // wrap your async call here
    const loadData = async () => {
      setUsersLoading(true);
      const result = await getUsers();
      setUsers(result);
      setUsersLoading(false);
    };

    // then call it here
    loadData();
  }, []);

  return (
    <div>
      <h1>Users</h1>
      {usersLoading && <div>Loading...</div>}
      <div>
        {users.map((user) => (
          <div key={user.id}>{user.username}</div>
        ))}
      </div>
    </div>
  );
};Code language: JavaScript (javascript)

Suggestion : 6

React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing React Hook Warnings for async function in useEffect Type Error : destroy is not a function in Reactjs while call api async function in useEffect hook React Hook "useState" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function

function Example() {
  const [data, dataSet] = useState<any>(null)

  useEffect(() => {
    async function fetchMyAPI() {
      let response = await fetch('api/data')
      response = await response.json()
      dataSet(response)
    }

    fetchMyAPI()
  }, [])

  return <div>{JSON.stringify(data)}</div>
}
2._
const response = MyAPIResource.read();

If you want to use functions outside with eslint.

 function OutsideUsageExample({ userId }) {
  const [data, dataSet] = useState<any>(null)

  const fetchMyAPI = useCallback(async () => {
    let response = await fetch('api/data/' + userId)
    response = await response.json()
    dataSet(response)
  }, [userId]) // if userId changes, useEffect will run again

  useEffect(() => {
    fetchMyAPI()
  }, [fetchMyAPI])

  return (
    <div>
      <div>data: {JSON.stringify(data)}</div>
      <div>
        <button onClick={fetchMyAPI}>manual fetch</button>
      </div>
    </div>
  )
}

Wrap it into a useCallback and use it as a dependency of the useEffect hook.

const getPosts = useCallback(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);
   }
}, []);

useEffect(async () => {
   getPosts();
}, [getPosts]);

Please try this

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

Suggestion : 7

This page describes the APIs for the built-in Hooks in React. Often, effects create resources that need to be cleaned up before the component leaves the screen, such as a subscription or timer ID. To do this, the function passed to useEffect may return a clean-up function. For example, to create a subscription: Instead, use useEffect. The function passed to useEffect will run after the render is committed to the screen. Think of effects as an escape hatch from React’s purely functional world into the imperative world.

1._
const [state, setState] = useState(initialState);
2._
setState(newState);
3._
function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}
5._
const [state, setState] = useState(() => {
   const initialState = someExpensiveComputation(props);
   return initialState;
});
6._
useEffect(didUpdate);

Suggestion : 8

componentDidMount, componentDidUpdate, componentWillUnmount: The useEffect Hook can express all combinations of these (including less common cases). render: This is the function component body itself. Idiomatic code using Hooks doesn’t need the deep component tree nesting that is prevalent in codebases that use higher-order components, render props, and context. With smaller component trees, React has less work to do. Any function inside a component, including event handlers and effects, “sees” the props and state from the render it was created in. For example, consider code like this:

1._
function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
2._
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';import Counter from './Counter';

let container;

beforeEach(() => {
  container = document.createElement('div');
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

it('can render and update a counter', () => {
  // Test first render and effect
  act(() => {    ReactDOM.createRoot(container).render(<Counter />);  });  const button = container.querySelector('button');
  const label = container.querySelector('p');
  expect(label.textContent).toBe('You clicked 0 times');
  expect(document.title).toBe('You clicked 0 times');

  // Test second render and effect
  act(() => {    button.dispatchEvent(new MouseEvent('click', {bubbles: true}));  });  expect(label.textContent).toBe('You clicked 1 times');
  expect(document.title).toBe('You clicked 1 times');
});
3._
function Timer() {
   const intervalRef = useRef();
   useEffect(() => {
      const id = setInterval(() => {
         // ...
      });
      intervalRef.current = id;
      return () => {
         clearInterval(intervalRef.current);
      };
   });

   // ...
}
5._
function Box() {
   const [state, setState] = useState({
      left: 0,
      top: 0,
      width: 100,
      height: 100
   });
   // ...
}
6._
  // ...
  useEffect(() => {
           function handleWindowMouseMove(e) {
              // Spreading "...state" ensures we don't "lose" width and height      setState(state => ({ ...state, left: e.pageX, top: e.pageY }));    }
              // Note: this implementation is a bit simplified
              window.addEventListener('mousemove', handleWindowMouseMove);
              return () => window.removeEventListener('mousemove', handleWindowMouseMove);
           }, []);
        // ...

Suggestion : 9

My general concern is that async cleanups might lead to weird race conditions. It may be unwarranted but the concept itself sounds quite alarming to me and I’d like to discuss this, if possible. I’m worried though that a disposed component can still cause an unwanted side-effect in a parent. One can imagine some scenarios where that would matter. orchestrating animation - an unmounted component tells the parent to trigger some sort of animation. The reason why the animation should happen is owned by a child, but it’s also based on an additional timer because the reason might become invalid if the user performs some invalidating action quickly enough. It’s not obvious here that useLayoutEffect should be used here to achieve instant cleanup.

If you go with the async cleanups then there is no guarantee that a scheduled work (or just any listeners) would get cleaned up before you get rid of a component instance, so for example:

useEffect(() => {
   if (state !== 'foo') return
   const id = setTimeout(() => setShouldAnimate(true), 300)
   return () => clearTimeout(id)
}, [state])

I have a similar concern about this with maybe a more concrete example;

const MyComponent = ({ destination }) => {
  const handleMessage = useCallback((e) => {
    fetch(destination); // or some other state changing action, such as mutating a reducer in a context
  }, [destination]);

  useEffect(() => {
    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, [handleMessage]);

  return (<div>Listening for messages</div>);
}
3._
const MyParentComponent = () => {
  const narrowView = useIsWindowNarrow();

  if (narrowView) {
    return (<section><MyComponent /></section>);
  } else {
    return (<section><div><MyComponent /></div></section>); // (i.e. something which prevents reusing the existing MyComponent)
  }
}