UseEffect still runs even if fetch data doesnt change - reactjs

i have this UseEffect() code and even if i pass the 2nd paramenter it just keeps re-rendering even if the data doesn't change, Any idea? hope to get a solution soon (MERN)
FRONTEND
const [qty, setQty] = useState([])
useEffect(() => {
fetch('/QtyCheck',{
headers:{
"Content-Type":"application/json"
}
})
.then(res=>res.json())
.then(qtyValue=>{
setQty(qtyValue)
})
},[qty])enter code here
BACKEND
router.get('/QtyCheck',(req,res)=>{
Post.find({},{qty:1,minT:1,criT:1, code:1,name:1,isDisable:1})
.then(postFind=>{
if(postFind.length===0){
res.json("nil")
}else{
res.json(postFind)
}
}).catch(noPost=>{
res.json("Error",noPost)
})
})

I think you are making an infinite loop here because how you are setup the end of brackets [qty]
render > useEffect > fetchData > [Qty](that mean data change) >re-render >useEfect > so on
the most important part here is the empty brackets []
instead you can make a separate function like :
const fetchdata = () =>
{
fetch('/QtyCheck',{
headers:{
"Content-Type":"application/json"
}
})
.then(res=>res.json())
.then(qtyValue=>{
setQty(qtyValue)
})
}
useEffect(()=>{
fetchdata()
},[])`

Your state is an array. That means even when fetch returns the same content result it will have different reference. React won't look at deep level to compare, it does a shallow, and each array has different reference, hence multiples fetches. To solve you could pass to dependency array a stringified version [JSON.stringify(qty)] or do some conditional deep comparasion at your hook before sending the next request.
Regardless, it seems you also need an open channel between back and front, so everytime there is an update on back it sends back to front, since you say it changes from time to time at backend regardless frontend interaction. If that's the case you can use libs like socket.io to create this channel between back and front.

Related

React app keeps sending continuous HTTP requests

I have a page that displays data fetched from a MongoDb through API, in this page, you can modify the data and after that, the page will render again to display the new data. But inspecting the network requests I noticed my react app sends an infinite number of requests, which obviously slows down everything. I read this is caused by this snippet of code:
useEffect(() => {
fetchData();
}, [users]);
I also read I must empty the dependencies array of the useEffect, but If I do so, the page will not re-render if the data changes (for example after inserting a new record in the db).
This is the function I use to get the data from the db:
const [users, setUsers] = useState([]);
async function fetchData() {
const res = await fetch("http://localhost:8000/users/");
if (res.status === 401) {
console.log(res.json);
} else {
setUsers(await res.json());
}
}
How can I fix this? Thanks.
You created an infinite loop:
fetchData calls setUsers, which sets users. The effect reacts to changes to users, and calls fetchData again. ♾️
I don't know your exact use case, but one solution would be to only call fetchData when an actual user interaction has happend in your app that makes you want to fetch new data.

Can't stop multiple calls to external API or get data to render in browser

I'm trying to add the user's local weather to my site using openweathermap.org's free API. The API call works; I get want I want back. But then it keeps calling the API hundreds of times. I also am having problems getting the data to render in the browser, which I think is an asynchronous Javascript problem. I have tried moving the function that triggers the fetch call, putting it in a useEffect(), using a setTimeout() and nothing works. When it's in the useEffect(), I get an error pointing to an issue 'reading 'latitude' in the fetch call.
So how do I solve those issues? And is there a way to store the latitude and longitude in a useState()?
FETCH CALL
export const getWeather = (coords, API) => {
return fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${coords.latitude}&lon=${coords.longitude}&units=metric&appid=${API}`)
.then(res => res.json())
}
CODE
export const Weather = () => {
const [weather, setWeather] = useState({})
const APIkey = "(redacted)"
const { coords, isGeolocationAvailable, isGeolocationEnabled } =
useGeolocated({
positionOptions: {
enableHighAccuracy: false,
},
userDecisionTimeout: 5000,
});
getWeather(coords, API).then(data => {setWeather(data)})
return !isGeolocationAvailable ? (
<div>Your browser does not support Geolocation</div>
) : !isGeolocationEnabled ? (
<div>Geolocation is not enabled</div>
) : coords ? (
<div><p>Your weather at {coords.latitude} and {coords.longitude} {weather?.name}, is {Math.round(weather?.main?.temp)}</p>
<p>High/low: {Math.round(weather?.main?.temp_max)}/{Math.round(weather?.main?.temp_min)}</p></div>
) : (
<div>Getting the location data… </div>
);
};```
That happens because in JSX, all of the code gets executed during each re-render. So each time your component re-render, the getWeather(...) will be called once, which is kind of scary.
useEffect is the right tool to use for things like on-mount API call, I'm not sure what "'reading 'latitude' in the fetch call" really means, maybe you can try this:
useEffect(() => {
if (coords && isGeolocationAvailable && isGeolocationEnabled) {
getWeather(coords, APIkey).then(setWeather)
}
}, [coords, isGeolocationAvailable, isGeolocationEnabled])
Notes:
If APIkey is sensitive data, you probably shouldn't put it right in your component. Try using dotenv or something else to store those variables, depending on your development environment.
The code in useEffect will be executed every time when any of its dependencies changes. So if you only want it to run once, either carefully select what should be put into dependency list, or use a boolean flag to prevent it from calling more than one time.

React cannot set state inside useEffect does not update state in the current cycle but updates state in the next cycle. What could be causing this?

I am using the useEffect hook for a get request using axios and consequently updating the state with some of the received data. This is my useEffect hook and the function inside:
const [expressions, setExpressions] = useState(expressionData.data);
const getFilteredExp = () => {
console.log(catFilter);
let config = {
method: 'get',
url: `/expressions/category/${catFilter}`,
headers: {}
};
axios(config)
.then((response) => {
console.log(response.data);
const newExp = response.data.payload.data;
console.log(expressions);
setExpressions(newExp);
console.log(expressions);
})
.catch((err) => {
console.error(err);
})
}
useEffect(() => {
if (!catFilter) {
setExpressions(expressionData.data)
console.log(expressionData.data);
}
else {
getFilteredExp();
}
}, [catFilter])
catFilter is also a state variable. My expressions state variable is not updated directly after the setExpressions call but one render cycle after. Not sure how to fix this.The console logs inside the getFilteredExp function should highlight my problem perfectly. These console logs should show my problem Everytime I change the category filter (catFilter state variable), the page is re-rendered and the previous get request's results are updated in the state. Ideally, the logs should return with 5 items after the get request returns with an array of 5 items. This is clearly not shown in the console log.
Additionally, even though my state is updated a render-late, my page content that depends on the expressions state variable is not being updated and remains in its default state.
Any help would be much appreciated and I am sorry if my question is not very clear or follow some requirements, I am relatively new to this.
The 2 console.logs here will display the same value for expressions, because they are referencing the same copy of expressions:
console.log(expressions);
setExpressions(newExp);
console.log(expressions);
If you want to see the changed value of expressions, you can add a useEffect() that triggers after expressions has had a chance to change:
useEffect(() => {
console.log(expressions);
}, [expressions]);
I'm not sure if there's enough info here to give a specific solution to the other problem. Add more info if you are still stuck.

state in useEffect second arguement Infinite loop

i have this small piece of code where i think i guess I'm doing it wrong
const [orders, setOrders] = useState([])
useEffect(() => {
fetch('/getOrders',{
headers:{
"Content-Type":"application/json",
"Authorization": "Bearer "+localStorage.getItem("jwt")
}
}).then(res=>res.json())
.then(orderList=>{
setOrders(orderList)
//console.log("myAdmin",orderList)
}).catch(err=>{
console.log("Error in Catch",err)
})
}, [orders])
Here the data is updated overtime by itself and i want my state updated every time the fetch data is different to the existing state , but regardless if the data is different or not, the state keep updating making the component re-render infinitely. Is there a solution for this?
Issue
Anytime an effect unconditionally updates a value that is in its dependency array it will cause render looping.
useEffect(() => {
fetch('/getOrders', { ... })
.then(res=>res.json())
.then(orderList=>{
setOrders(orderList); // <-- updates state, triggers rerender
})
.catch(err=>{
...
});
}, [orders]); // <-- updated state triggers effect callback
Possible Solution
It appears you are wanting to fetch every time the component renders and upon successful response (200 OK) check if the orders array is actually different. If it is then update state, otherwise ignore the update.
Remove the dependency array so the useEffect hook callback is called each time the component renders.
Check if the fetch request response is ok before accessing the response JSON data.
Check the order response data against state. If the arrays are different lengths, update state, otherwise you will need to check element by element to determine if the array is different or not. *
Updated effect hook
const [orders, setOrders] = useState([])
useEffect(() => {
fetch("/getOrders", {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + localStorage.getItem("jwt")
}
})
.then((res) => {
if (!res.ok) {
throw new Error("Fetch is not OK");
}
return res.json();
})
.then((orderList) => {
if (orders.length !== orderList.length) {
setOrders(orderList);
} else {
for (let i = 0; i < orders.length; i++) {
// *** Your actual equality condition may be different, i.e. if they are objects ***
if (orders[i] !== orderList[i]) {
setOrders(orderList);
return;
}
}
}
})
.catch((err) => {
console.error("Error in Catch", err);
});
});
* This is an O(n) check every time so an optimization that can be made may be to also return and store an "update" hash. If the hash is the same as the previous request, skip state update. This could be something as simple as generating a new GUID for the update in backend and returning it with data.
Basicaly [orders] in your code means: call what inside useEffect each time orders change, and each time useEffect is called , it update again the orders ... and you get your infinite loop
useEffect(() => {
}, [orders])
you should change [orders] into [].
res=>res.json() will create a new refrance to your object, and changing your referance to your object means it changed, for example [1,2,3] if you pass again [1,2,3] it will not be the same objct even thow it looks the same. v=[1,2,3], you pass v, and then v[0]=-1, passe v again it will be considred the same since it has the same referance.
Best way to fix this is to compare them manualy depending on your case, and when they aren't the same, you use somthing like [...orders] which will create new refrence and it will invoke useEffect again.
try to set state with prevState argument, so you will not have to include any dependency.
setOrders((prevState) => ({
...prevState, orderList
}))

Using context in React while using useEffect

Ok, I am pulling my hair out.
I am trying to utilize useEffect in my context to populate a list on screen.
useEffect(() => {
Axios.get("http://localhost:8080/getItems").then((response) => {
console.log(response);
setItems(response.data);
});
}, [items]);
This allows me to populate the list each time I add an item to the database. However, it causes an endless loop in Spring.
Now, I can stop the endless loop by changing [items] to []. Yet, when I add to my database it no longer renders the change on the screen.
Does anyone know a workaround to the problem? To be exact, I still want to render an updates to the database without having an endless loop
If you update your database you are not going to receive the changes in your frontend if you don't perform a request to get them.
There are a several mechanims to achieve that:
Pull (every X period of time you perform a request asking for changes)
Push your backend notifies frontend when a new change is performed.
Pull mechanisn is the one you called endless loop.
Push mechanins you need to use websockets for example or a library like socket.io to perform it.
First you change items array which fires useEffect, after when items are fetched you change it again and useEffect fires again. Thats why you get endless loop.
You can try to do something like this:
const [isLoaded, setIsLoaded] = useState(false);
const pushItem = (item) => { // Place where you add new item.
setItems([...items, item]);
setIsLoaded(false);
};
useEffect(() => {
if (isLoaded) {
return;
}
Axios.get("http://localhost:8080/getItems").then((response) => {
setItems(response.data);
setIsLoaded(true);
});
}, [items, isLoaded]);

Resources