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 togetTodos
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
Difference Between Call and Apply
How to Get the Caret Column (Not Pixels) Position in a Textarea, in Characters, from the Start
Get the Name of an Object'S Type
JavaScript Open in a New Window, Not Tab
How to Get Hex Color Value Rather Than Rgb Value
Smooth Scrolling When Clicking an Anchor Link
Difference Between Object Keys With Quotes and Without Quotes
Why Would a JavaScript Variable Start With a Dollar Sign
Resizing an Iframe Based on Content
JavaScript - Sort Array Based on Another Array
JavaScript Raises Syntaxerror With Data Rendered in Jinja Template
How to Get All Properties Values of a JavaScript Object (Without Knowing the Keys)
Working With $Scope.$Emit and $Scope.$On
How to Check If a String "Startswith" Another String
Window.Onload VS $(Document).Ready()