Setting state with setState doesn't change values - reactjs

I am calling fetchProducts in a loop (array.map) from a child component, but don't want have to call it when it's already been done before. Just for testing, I have added "i" which I update after the first call, but this.state.i === 0 is always true, so I keep fetching the same data.
fetchProducts = async (id) => {
if(this.state.i === 0) {
let url = "http://localhost/mysite/wp-json/mysite/product/" + id
await fetch(url)
.then(response => response.json())
.then(result => {
this.setState({
...this.state,
products: this.state.products.concat([result]),
i: 1
})
})
}
}
Am I missing something here? Or is there a better way to cache it? The idea is to check for a product ID in this.state.products, instead of "i".

I have two observations.
Firstly, you are setting the state once a promise is resolved.
Secondly, you are calling the fetchProducts through map, which is synchronous.
So, what's happening is, your fetchProducts is keeps on getting called synchronously. That means it doesn't wait for your previous promise to resolve. But you are setting your state after the promise resolved. Hence, it is not guaranteed that when your consecutive fetchProducts are called you would have set the state in the previous function calls.

Related

React setState inside useEffect async

I am attempting to perform a series of Axios requests inside the useEffect() of a react component. I am aware that these requests are asynchronous, and I should maintain a piece of "loading" state that specifies if series of requests have been completed.
const [state, updateState] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
let innerstate = []
allRespData.map(single_response => {
axios.post("<URL>", {
raw_narrative: single_response[index].response
})
.then((response) => {
innerstate.push(response.data)
});
})
updateState(innerstate)
setLoading(false)
}, []);
if (loading)
return (<h3> Loading </h3>)
else {
console.log(state)
return (<h3> Done </h3>)
}
I would expect the output from the above code to be a list containing the data of each response. Unfortunately, I think that data only arrives midway through the console.log() statement, as initially an empty list [] is logged, however the list is expandable- therein my expected content is visible.
I am having a hard time doing anything with my state at the top, because the list length is constantly 0, even if the response has already loaded (loading == false).
How can I assert that state has been updated? I assume the problem is that the loading variable only ensures that a call to the updateState() has been made, and does not ensure that the state has actually been updated immediately thereafter. How can I ensure that my state contains a list of response data so that I can continue doing operations on the response data, for example, state.forEach().
You're not awaiting any of the requests, so updateState will get called before any of the responses have had time to come back. You'll be setting the state as [] every time. You also need to return your axios.post or the data won't get passed to .then
There are lot of nicer ways to handle this (I'd recommend looking at the react-query library, for example). However, to make this work as it is, you could just use Promise.all(). Something like:
useEffect(() => {
Promise.all(
allRespData.map(single_response =>
axios
.post('<URL>', { raw_narrative: single_response[index].response })
.then(response => response.data)
.catch(error => {
// A single error occurred
console.error(error);
// you can throw the error here if you want Promise.all to fail (or just remove this catch)
})
)
)
// `then` will only be called when all promises are resolved
.then(responses => updateState(responses))
// add a `.catch` if you want to handle errors
.finally(() => setLoading(false));
}, []);

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

Can't set state with axios get request?

So I want to set my state for my bookings to be my result.data, the request is working and I can console.log(result.data). However when I try to set the state and then check that state, it's empty.
What is going on? Does it have something to do with my params I'm passing to axios?
My initial state looks like: this.state = {bookingArrayByDate: []}
Tried setting the state outside "then" but that didn't work either
componentDidMount() {
let today = moment(new Date())
let dateToSend = today.format('YYYY-MM-DD')
axios
.get(
`http://localhost:8888/booking-backend/fetch-reservation.php/`,
{ params: { res_date: dateToSend } }
)
.then((result: any) => {
console.log(result.data)
this.setState({
bookingArrayByDate: result.data
})
})
console.log('this is outside', this.state.bookingArrayByDate)
}
The array at the bottom will be empty when i console log it.
Your code is fine. What you are experiencing is the standard asynchronous behavior ofthis.setState(). When you console.log() your state, there is no guarantee that it was updated in the time between your setState() and the console.log() execution.
In fact, its almost 100% unlikely that it was changed within that time, because React actually batches setState() requests by putting them in a queue until the entire event is completed. In your case, the setState() wouldn't actually occur until after the componentDidMount() logic finishes.
What you can do is pass a call-back as the 2nd argument to this.setState(), that call-back is executed directly after the state has finished updating, thus ensuring you are printing the updated state.
componentDidMount() {
let today = moment(new Date())
let dateToSend = today.format('YYYY-MM-DD')
axios
.get(
`http://localhost:8888/booking-backend/fetch-reservation.php/`,
{ params: { res_date: dateToSend } }
)
.then((result: any) => {
this.setState({
bookingArrayByDate: result.data
}, () => console.log(this.state.bookingArrayByDate)) <-- you can swap this whatever function you need.
})
}

how to remove lag in setState using callback function in react

how to remove lag in setState using callback function in react
tried using callback but still lag state and data in array state cannot be mapped
mapfn(){
ServerAddr.get(`/dishes/read/meal/category`)
.then(res => {
const getmeal6 = res['data']['data'];
this.setState({ getmeal6 },()=>{
console.log('log233',this.state.getmeal6);
});
});
console.log('log232',this.state.getmeal6);
this.state.getmeal6.map((item) => {
return (
this.setState({
maparr:[...this.state.maparr,item.id],
})
);
});
console.log(this.state.maparr,'val32');
}```
in log233 the state is proper but in log232 the state lags with 1
The problem with your current code is that both http calls, and calls to setState are asynchronous.
// this call is asynchronous
ServerAddr.get(`/dishes/read/meal/category`)
.then(res => {
const getmeal6 = res['data']['data'];
// this is also asynchronous
this.setState({ getmeal6 },()=>{
});
});
// this call happens synchronously! It will almost certainly happen before the two
// async calls complete
this.state.getmeal6.map((item) => {
return (
this.setState({
maparr:[...this.state.maparr,item.id],
})
);
});
If you want to do something after your http call and the setState are both resolved, you need to either be inside the then function of a promise, or in the callback function of setState.
So something like this:
// this call is asynchronous
ServerAddr.get(`/dishes/read/meal/category`)
.then(res => {
const getmeal6 = res['data']['data'];
// this is also asynchronous
this.setState({ getmeal6 },()=>{
// this is where you need to put the
// code you want to happen after the http call and setState
});
});
That said, you need to reconsider what you are trying to do - either by refactoring your state management using something like Redux, or by using async await in your method, to make your code a little easier to read, or by a totally new approach to the problem at hand.

Get pathname from window and store it in state to use in fetch function

I have the following function:
fetchData = () => {
fetch('/api/customerNodes' + this.state.number)
.then(response => response.text())
.then(message => {
this.setState({responseText: message});
});
};
If I point my browser to localhost:3000/1234 and I want to get the pathname of 1234, I do something like this. const num = window.location.pathname;
The problem I face is im not able to set that value to state.
componentDidMount() {
const num = window.location.pathname;
this.setState({ number: num });
console.log(this.state.number);
//setInterval(this.fetchData, 250);
}
The console.log is empty.
What am I doing wrong?
Use the callback function in this.setState
const num = window.location.pathname;
this.setState({ number: num }, () => {
console.log(this.state.number, 'number');
});
setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet.By putting the log in the callback of the setState() method it is executed after the state change is complete.
setState is asynchronous and you can't expect it to execute before the next line of code.
From the docs:
React may batch multiple setState() calls into a single update for performance.
setState is async method. If you log state in render method, you can get the number.

Resources