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: useEffect function must return a cleanup function or nothing

I suggest to look at Dan Abramov (one of the React core maintainers) answer here:

I think you're making it more complicated than it needs to be.

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

Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like

const response = MyAPIResource.read();

and no effects. But in the meantime you can move the async stuff to a separate function and call it.

You can read more about experimental suspense here.


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

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

Warning: An effect function must not return anything besides a function, which is used for clean-up

useEffect(() => {
async function check() {
var item = await AsyncStorage.getItem("User");
console.log(item);
if (item == null || item == undefined) {
props.navigation.navigate("Login");
}
else {
var user = await JSON.parse(item);
if(user.fullname=="admin"){
props.navigation.navigate("AdminHP");
}
else{
props.navigation.navigate("UserHP");
}
}
console.log("effect");
}

check()
}, [])

Proper async await syntax for fetching data using useEffect hook in React?

You should not make your useEffect function async, but you can create an async function inside useEffect

useEffect(() => {
const getDatas = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
setApiData(data);
}
getDatas()
});

or even

useEffect(() => {
(async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
setApiData(data);
)()
});

useEffect cleanup on 'imported' async function

You can try this approach instead. Keep track of the mounted status in the hook itself, as well as the conditional setState function.

Also it is important that you include the empty dependency array, otherwise your component will call the hook on every render.

import { fetchFunction } from '../../../somewhereInTheDir';

function Page() {
const [data, setData] = useState([]);
const [isMounted, setIsMounted] = useState(true);

async function fetchData() {
try {
return await fetchFunction(someUri);
} catch (error) {
console.warn(error);
}
}

useEffect(() => { // <- you can't change this to async
let mounted = true

// Variant with Promise
fetchData(ENDPOINT).then(data => {
if (mounted) setData(data);
})

// Variant with async/await
(async () => {
const data = await fetchData(ENDPOINT)
if (mounted) setData(data);
}())

return () => {
mounted = false
}
}, []) // <- empty dependency array is important here
}

The following article introduces few other methods how to handle the mounting state even if you use multiple useEffect hooks, etc.

https://www.benmvp.com/blog/handling-async-react-component-effects-after-unmount/

reactjs useEffect cleanup function after fetching data

This could be handled in multiple ways.

  • You could have mountedTodos as a global variable outside the component.

  • You could defined mountedTodos as an object and pass it to getTodos function.

However, I suggest you to use an AbortController

const getTodos = async (signal) => {        
try {
const response = await fetch(/*url*/, { signal });
const todos = await response.json();
if(!signal.aborted) {
setTodos(todos);
}
}catch(e) {
console.log(e);
}
}

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

return () => abortController.abort();
},[])

Note: You should add getTodos in the dependency array of the useEffect hook and to avoid infinite loop of state update and re-render, wrap getTodos in useCallback hook.

React useEffect hooks return () = cleanup() vs return cleanup

It's normally fine to return a cleanup function directly, rather than wrapping it in an additional arrow function. The only reason the additional function is needed in this case because destroy uses this.

For regular functions, the value of this is determined by how the function is called. So if you have code that says view.destroy(), then the characters view. are the reason that this gets set to view. That's why () => view.destroy() one works: you are explicitly saying what this should be, as you call the function.

But if you just return view.destroy, you are not calling the function, just returning a reference of it to react. React doesn't know anything about view, it just knows you returned a function. So when react later calls your function, it doesn't know what to set this to, and so this gets set to undefined. Since it's undefined, this.plugins causes the error you see.



Related Topics



Leave a reply



Submit