Avoid useEffect to be triggered when one nested dependency changes - reactjs

My form has 3 fields, lambda, period and filterPattern. When the values of lambda and period change, it will trigger handleSearch. However, I don't want handleSearch to be triggered when filterPattern changes.
If I don't put filterPattern in the dependency array, then handleSearch cannot get the latest value of filterPattern.
How to avoid useEffect to be triggered when one nested dependency changes? Thanks.
const getLogQuery = useCallback(() => ({
lambda,
period,
filterPattern,
}), [lambda, period, filterPattern]);
const handleSearch = useCallback(async () => {
await getLambdaLogs(getLogQuery());
}, [getLogQuery]);
useEffect(() => {
handleSearch();
}, [handleSearch]);
const getLambdaLogs = async (query) => {
const logs = await LambdaService.getLambdaLogs(query);
setLogItems(logs);
}

I'm making a few assumptions, but the second argument of useEffect are its dependencies, the things it watches to know when to fire. So instead of
useEffect(() => {
handleSearch();
}, [handleSearch]);
Which doesn't make any sense, to watch a function and then call that function if it changes, change that to
useEffect(() => {
handleSearch();
}, [lamba, period]);
```

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

Is it ok to update state very second?

I am trying to display the count of items in a cart. I want the count to update every second so if a user adds or deletes from cart it will make that request every second and refresh, updating the state and the number will change. I just tried this method and it works fine, but I'd like to know if it's ok to do or if there is a better way of doing it.
const [update, setUpdate] = useState(0)
const [data, setData] = useState([])
let currentUser = 1
const getData = () => {
axios.get(`http://localhost:4000/api/userCart/${currentUser}`)
.then((res) => {
setData(res.data)
setUpdate(++update)
})
}
useEffect(() => {
getData()
}, [update])
useEffect(() => {
setInterval(() => {
getData()
}, 1000);
},[])
I think thats ok, you need just a way to clear this interval when you destroy the component
const timer = useRef<any>(null);
timer.current = setInterval(() => {
//your interval code
}, time);
useEffect(()=>{
return () => {
clearInterval(timer.current);
}
},[])
your first useEffect I think can be a problem, you made a interval and a effect that runs every get
It's okay when you want to give a real-time like experience. If this will be on production you need to consider how many request will be done and the time it can take to resolve and get the data.
There's a pacakge SWR from Vercel team which you can use https://swr.vercel.app/docs/revalidation , it fetches data, validates it's state and serves a cached state when available. Give it a try
If you want to continue with your own implementation then you need to take into consideration this:
Intervals will keep fetching data don't caring if previous fetch was completed. Solution: Fetch data then run a setTimeout and resolve with a new fetch
Clean up. Save each timeout in a Ref and when a component unmounts clear that timeOut
There's no correct way of doing stuff, give any idea you have a try and if it works the just polish it and avoid any side effects as the mentioned above : )
To consider in your current code
In the code you shared, the getData function is being invoked twice, one from interval which then keeps requestin data, and again when you update the update prop.
A refactor idea can be this:
// Out of component body
const UPDATE_INTERVAL = 1000
// In component body
const [update, setUpdate] = useState(0)
const [data, setData] = useState([])
const timer = useRef(null)
useEffect(() => {
const triggerUpdate = setUpdate((n) => n + 1)
const getData = () => {
return axios.get(`http://localhost:4000/api/userCart/${currentUser}`)
}
getData()
.then((res) => {
setData(res.data)
timer.current = setTimeout(triggerUpdate, UPDATE_INTERVAL)
})
.catch(console.error)
return () => {
clearTimeout(timer.current)
}
}, [update])

How to control the execution timing of useEffect

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.

React state hook doesn't properly handle async data

I'm trying to set a component's state through an effect hook that handles the backend API. Since this is just a mock, I'd like to use the vanilla react methods and not something like redux-saga.
The problem is that while the fetching part works, the useState hook doesn't update the state.
const [odds, setOdds] = useState({})
useEffect(() => {
(async () => {
fetchMock.once('odds', mocks.odds)
let data = await fetch('odds').then(response => response.json())
setOdds(data)
console.log(odds, data) // {}, {...actual data}
})()
}, [])
I've tried to pipe the whole process on top of the fetch like
fetch('odds')
.then(res => res.json())
.then(data => setOdds(data))
.then(() => console.log(odds)) // is still {}
But it doesn't make a single difference.
What am I doing wrong?
Basically if you call setOdds, the value of odds does not change immediately. It is still the last reference available at decleration of the hook.
If you want to access the new value of odds after updating it, you would have to either use the source of the updated value (data) if you want to access the value in the same useEffect hook or create another useEffect hook that triggers only when odds has changed:
useEffect(() => {
console.log(odds);
// Do much more
}, [odds]) // <- Tells the hook to run when the variable `odds` has changed.
If you want to see that state has changed in here, you can use
const [odds, setOdds] = useState({})
useEffect(() => {
(async () => {
fetchMock.once('odds', mocks.odds)
let data = await fetch('odds').then(response => response.json())
setOdds(prevData => {
console.log(prevData, data) // {}, {...actual data}
return data
})
})()
}, [])

Resources