How to control the execution timing of useEffect - reactjs

When user click search button, reset current page to 1, and set filter object, and then call fetch function.
The projectId is a global variable, when it's changed, need to reset page and filter, and reload data.
useEffect require dependence of fetch function, but when fetch function change, it will re-execute fetch function.
How can I deal with this logic and not to trigger any warning of eslint?
Sorry, my English is not good.
const fetch = useCallback(async () => {
setLoading(true);
// Call the request function passed in from the outside
const { data, total } = await request({ current: currPage }, filter);
setList(data);
setTotal(total);
setLoading(false);
}, [currPage, filter, request]);
useEffect(() => {
setCurrPage(1);
setFilter({});
fetch();
}, [fetch, projectId]);

I think there is no need to keep fetch in the dependency array of useEffect When the projectId changes you will change the currPage & filter which will automatically trigger the useEffect which has the fetch() function in it.
const fetch = useCallback(async () => {
setLoading(true);
// Call the request function passed in from the outside
const { data, total } = await request({ current: currPage }, filter);
setList(data);
setTotal(total);
setLoading(false);
}, [currPage, filter, request]);
useEffect(() => {
setCurrPage(1);
setFilter({});
fetch();
}, [projectId]);
I suggested this code serves the issue.

Related

API is getting called inside useEffet multiple times (React js)

I am calling an function inside useEffect but it is rendering multiple times.
let courseVideo = async () => {
const response = await api
.post(COURSE_VDO_URL, JSON.stringify({ courseID, username }), {
headers: { "Content-Type": "application/json" },
"Access-Control-Allow-Credentials": true,
})
.then((data) => {
setCoursesVdoList(data.data.data.lessons);
setCompletedEpisode(data.data.data.lessonsCompleted)
setExistingCourseID(data.data.data.courseID)
});
};
useEffect(() => {
courseVideo();
const interval = setInterval(() => {
setCount(!count)
}, 300000)
}, [count, completedEpisode]);
the count is used because I want to force rerender the component after 5 minutes.
You can try using functional updater and clearing the timer
useEffect(() => {
let ignore = false;
// depends on courseID, username
// need to include them in depedency
courseVideo();
const interval = setInterval(() => {
if (ignore) {
// skip setting state on unmounted component
return;
}
// use functional updater to remove dependency on count
// as this will stop infinite render cycles
setCount(c => !c)
// without clearing this could trigger multiple times
}, 300000)
return () => {
ignore = true;
clearInterval(interval)
}
}, [completedEpisode]); //include courseID, username dependencies, they are used in courseVideo
You can read more about useEffect life cycle in the new beta docs
Hope it helps
The courseVideo function is being called every time the component re-renders. It is being passed as a dependency to the useEffect hook. That why, it's multiples time.
You need to only call it when the component is initially rendered. You can clean up interval when the component unmounts.
useEffect(() => {
courseVideo();
const interval = setInterval(() => {
setCount((prevCount) => !prevCount);
}, 300000);
return () => clearInterval(interval);
}, []);
you can write another useEffect which one render only initial rendered time.
and if the courseID and username change dynamically then should be added this two variable in dependancy list otherwise leave it empty.
useEffect(()=> {
courseVideo()
},[courseID, username])

Refactoring useEffect to only require one database call

At the moment, I have a component which completes some backend calls to decide when to start displaying the UI.
It's structured like this:
useEffect(() => {
getData()
})
const getData = async () => {
await verifyUser()
await fetchData()
}
The purpose here, is that verifyUser() is supposed to run first, and in the response to verifyUser(), a user id is provided by the backend.
const verifyUser = async () => {
if (!localStorage.getItem('auth')) {
return
}
if (localStorage.getItem('auth')) {
await axios.post("/api/checkAuth", {
token: JSON.parse(localStorage.getItem('auth'))
})
.then((response) => {
return setUserId(response.data.user_id)
})
.catch((err) => {
console.log(err)
localStorage.removeItem('auth')
})
}
}
As a result of this, the fetchData() function is supposed to wait until the verifyUser() function has stopped resolving, so it can use the user id in the database query.
However, at the moment it...
Calls once, without the user id
Then calls again, with the user id (and therefore resolves successfully)
Here's the function for reference:
const fetchData = async () => {
console.log("Fetch data called.")
console.log(userId)
await axios.post("/api/fetch/fetchDetails", {
user_id: userId
})
.then((response) => {
// Sets user details in here...
return response
})
.then(() => {
return setFetching(false)
})
.catch((err) => {
console.log(err)
})
}
What I'm trying to achieve here is to essentially remove any concurrency and just run the functions sequentially. I'm not 100% sure what the best practice here would be, so some feedback would be appreciated!
Your useEffect is missing a dependency array argument:
useEffect(() => {
getData()
})
should be:
useEffect(() => {
getData()
}, [])
Without that argument, useEffect will run once each time your component renders. With that argument, it will only run once, when the component is first mounted (ie. after the first render).
If you needed it to depend on another variable (eg. user.id isn't defined on load, but is later on) you could put that variable in the dependency array, ie.
useEffect(() => {
if (!user.id) return;
getData()
}, [user.id])
This version would run once when the component is mounted, then again if the user.id changes (eg. if it goes from null to an actual number).
In React, the useEffect hook accepts two arguments - the first one is a function (this is the "effect"), and the second one is a dependency array. The simplest useEffect hook looks like this:
useEffect(() => {
}, [])
The above hook has no dependency (because the array is empty), and runs only when the component initially mounts, and then goes silent.
If you don't pass in a dependency array as the second argument, as #machineghost said, the hook will run the "effect" function every time your component re-renders.
Now to your specific problem. You want to run fetchData after verifyUser has resolved its Promise, so you'd add the outcome of verifyUser as a dependency to a separate useEffect hook that calls fetchData. In this case, the outcome is setting userId.
So instead of this:
useEffect(() => {
getData()
})
const getData = async () => {
await verifyUser()
await fetchData()
}
Do this:
useEffect(() => {
verifyUser();
}, []);
useEffect(() => {
if (userId) { // assuming userId has a false-y value before verifyUser resolved
await fetchData();
}
}, [userId])

Read the setState value immediately after setting in useEffect React

I want to fetch info from the API in useEffect, set it using setState and immediately use it for further filtering.
the code looks like this:
const[usersInfo, setUsersInfo] = setState('')
useEffect(()=>{
async function fetchUsers(){
const response = await fetch(`http://localhost:8083/api/patient/findAll`);
const json = await response.json();
setUsersInfo(json)
}
fetchUsers()
console.log('users info', usersInfo)
},[])
I tried to pass the dependency for usersInfo but then it is running in loop.
What can I do it to prevent this behavior?
You need to add a second useEffect that detects changes to usersInfo.
const [usersInfo, setUsersInfo] = setState("");
useEffect(() => {
async function fetchUsers() {
const response = await fetch(`http://localhost:8083/api/patient/findAll`);
const json = await response.json();
setUsersInfo(json);
}
fetchUsers();
// console.log("users info", usersInfo); // commenting out this code since it will always print empty string
}, []);
useEffect(() => {
if(usersInfo !== ""){
// Do stuff here with usersInfo
}
}, [usersInfo]);

how to cancel useEffect subscriptions of firebase

I'm not quite understand how useEffect cleanup function work. Because whatever I do I always get warning:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Here is my code:
useEffect(() => {
setLoading(true)
// Get position list
const getPositionList = db.collection('lists').doc('positions').get()
.then( res => {
let data = JSON.stringify(res.data())
data = JSON.parse(data)
setPositionsList(data.list)
setLoading(false)
})
return () => getPositionList
}, [])
useEffect expects as a return value an unsubscribe/cleanup function, or a null. Your .get() is NOT a subscription, so there's nothing to clean up
BUT useEffect is NOT asynchronous, and the .get() definitively returns a promise that needs a delay, even with the .then(). Your dependency array is empty, so useEffect is only called once - but I suspect your component unmounted before the .get() ever returned.
Your code will need to be closer to:
useEffect(() => {
setLoading(true)
(async () => {
// Get position list
await db.collection('lists').doc('positions').get()
.then( res => {
let data = JSON.stringify(res.data())
data = JSON.parse(data)
setPositionsList(data.list)
setLoading(false)
});
)();
return null
}, [])
the ( async () => {})() creates an anonymous asynchronous function, then executes it. You do want to try to structure your system such that the enclosing component does not unmount before loading is set to false - we don't see that here, so we have to assume you do that part right.
The important take-aways:
useEffect is NOT asynchronous
.get() IS asynchronous
( async () => {}) creates an anonymous async function, and ( async () => {})() creates a self-executing anonymous asynchronous function
useEffect(() => {
setLoading(true)
// below firebase api return promise. hence no need to unsubscribe.
db.collection('lists').doc('positions').get()
.then( res => {
let data = JSON.stringify(res.data())
data = JSON.parse(data)
setPositionsList(data.list)
setLoading(false)
})
return () => {
// any clean up code, unsubscription goes here. unmounting phase
}
}, [])
According #Dennis Vash answer I figure out, that before setting state I have to check is component mounted or not, so I add variable in useEffect and before set state I check I add if statement:
useEffect(() => {
setLoading(true)
let mounted = false // <- add variable
// Get position list
db.collection('lists').doc('positions').get()
.then( res => {
let data = JSON.stringify(res.data())
data = JSON.parse(data)
if(!mounted){ // <- check is it false
setPositionsList(data.list)
setLoading(false)
}
})
return () => {
mounted = true // <- change to true
}
}, [])

How to setstate after fetch data React hook [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 10 months ago.
Code :
Result : Not have data in state
help me pls , thanks!
setState is asynchronous that's why you are seeing books as empty array. Here is a quote from the React docs:
The setState function is used to update the state. It accepts a new
state value and enqueues a re-render of the component.
One thing you may be doing wrong is in your useEffect callback. If your effect returns a function, React will run it when it is time to clean up. And you don't want the setState functions in fetchData to be invoked during clean up as the component will probably be unmounted.
If you just want the fetchData to only run once after the component mounts, here is a possible solution:
useEffect(() => {
// put the fetchData inside the effect
async function fetchData() {
setLoading(true);
const name = await getNameGroup();
const tmp = await getAll(name);
console.log(tmp);
setBooks(tmp);
console.log(books); // may not be the same as tmp, but you will see the updated state in the next render
setLoading(false);
}
fetchData();
},[]}
You should read more about useEffect hook in the React docs.
It's a stale closure problem.
Your useEffect where the fetchData is being called, has an empty dependency array. Within the fetchData function, which is inside useEffect, you are trying to print books which one first load, was initialized with an empty array.
All hooks hold the same reference to the variables with which they were initialized, till the dependencies change. To get an updated state, they depend on the dependency array. Since your dependency array doesn't specify books, it won't refresh the reference of books in your fetchData function either. Read more about the stale closure problem here
That's why your books variable is showing stale data.
export default function() {
// fetch data here
// runs only once because of empty dependency array
useEffect(() => {
let isCancelled = false
// define the fetchData inside the `useEffect` so that
// you can detect if the component has been unmounted
// using `isCancelled`
const fetchData = async () => {
const tmp = await getAll()
// only update state if component isn't unmounted
// if you try to update state on an unmounted component,
// React will throw an error
if (!isCancelled) {
setIsLoading(false)
setBooks(tmp)
}
}
if (!isCancelled) {
setIsLoading(true)
fetchData()
}
// cleanup
return () => {
isCancelled = true
}
}, [])
}
const [dataArray, setDataArray] = useState([]);
async function fetchData() {
try {
setIsLoading(true);
const response = await getNameGroup();
setDataArray(response);
} catch(error) {
// handle error
} finally {
setIsLoading(false);
}
}
This is an example code that is working and you can apply:
const [data, setData] = useState([]);
const [hasError, setErrors] = useState(false);
async function fetchData() {
const LibraryQuery = JSON.stringify({query: `query { species { id name description } }`});
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const res = await fetch('http://localhost:3000/graphql',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token
},
body: LibraryQuery
});
res
.json()
.then(res => setData(res.data))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);

Resources