how to remove lag in setState using callback function in react - reactjs

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.

Related

Setting state with setState doesn't change values

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.

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

the state is not updating after setting new

I am new to react and facing a problem. I am fetching data from an API using Axios then I have to set that data into state and pass that value in another component as props.
My problem is i am changing state using this.setState after fetching API , but the state is not changing. So I am sharing my code below.
constructor(props){
super(props);
this.state={
employeeData:[] // setting empty value
}
}
ComponentDidMount(){
console.log("Current State"+JSON.stringify(this.state)) ///output = []
axios.get("http://localhost:8080/hris/api/employee/get/all")
/// getting values , can see them in network
.then(response => response.data)
.then((data) => {
this.setState({ employeeData: data }) ///setting new value
console.log(this.state.employeeData) /// can see fetched data
})
.catch(err=> console.log(err))
console.log("2Nd STATE "+this.state) /// again empty state, there is no fetched data
}
Then I have to pass that state in another component.
render(){
return(
<div className=" col-md-12" style={viewData}>
<div >
<p><b>All Employee Details</b></p>
</div>
<Table data={this.state.employeeData}/>
</div>
)
}
setState is async function which takes some time to set your new state values. So printing new state after this line will give you previous state only and not new state.
You need a callback, to check the changed state,
this.setState({ employeeData: data }, () => console.log("2Nd STATE "+this.state))
Another thing is, axios is meant to reduce number of .then(). With axios you will get direct JSON value. You can remove 1 .then().
axios.get("http://localhost:8080/hris/api/employee/get/all") /// getting values , can see them in network
.then(response => {
this.setState({ employeeData: response.data }, () => console.log("2Nd STATE "+this.state)) // This will give you new state value. Also make sure your data is in `response.data` it might be just `response`.
console.log(this.state.employeeData) // This will give you previous state only
})
.catch(err=> console.log(err))
Your console.log("2Nd STATE "+this.state) is returning empty because it probably runs before that axios request completes.
Initially your render method gets called with empty state which is probably throwing an error. You need to handle the render with loading state until your request completes.
For example your render could look like this,
render() {
return (!this.state.employeeData.length) ?
(<div>Loading..</div>) :
(
<div className=" col-md-12" style={viewData}>
<div >
<p><b>All Employee Details</b></p>
</div>
<Table data={this.state.employeeData} />
</div>
)
}
setState is async so you cannot see the change instantly where setState() is called. in order to view the change, you need to do a callback.
this.setState({ employeeData: data },()=>console.log(this.state.employeeData)) ///setting new value
change the code to above format and you can see the change in state once it is changed
I guess this is where you are going wrong give it a try with this. It has got nothing to do with react. The way you used Axios is wrong.
ComponentDidMount(){
console.log("Current State" + JSON.stringify(this.state));
axios
.get("http://localhost:8080/hris/api/employee/get/all")
.then(response => {
this.setState({ employeeData: response.data });
})
.catch(err => console.log(err));
};
Alright, both of these problems are occurring because Axios and this.setState() are asynchronous. Its hard to explain asynchronous programming in a single StackOverflow answer, so I would recommend checking this link: [https://flaviocopes.com/javascript-callbacks/][1]
But for now to get your code to work, switch it to this
ComponentDidMount() {
console.log(this.state); // Obviously empty state at beginning
axios.get("http://localhost:8080/hris/api/employee/get/all")
.then(res => res.data)
.then(data => {
this.setState({employeeData: data}, () => { // Notice the additional function
console.log(this.state); // You'll see your changes to state
})
})
.catch(err => console.log(err));
console.log(this.state); // This won't work because Axios is asynchronous, so the state won't change until the callback from axios is fired
}
The part that most new React developers don't tend to realise is that this.setState() like axios is asynchronous, meaning the state doesn't change immediately, the task of actually doing that gets passed on as a background process. If you want to work with your state after it has changed, the this.setState() function provides a second parameter for doing just that
setState(stateChange[, callback])
taken from the react docs. Here the second parameter is a callback (a.k.a function) you can pass that will only get triggered after the state change occurs
// Assuming state = {name: "nothing"}
this.setState({name: "something"}, () => {
console.log(this.state.name); // logs "something"
});
console.log(this.state.name); //logs "nothing"
Hope this helps!!.

How to cancel promise in componentWillUnmount() without using this.isMounted?

I have to call API when the user stops typing and it is working perfectly fine. And I have to mount when the enter key is pressed.
I made a Mock Component here which does this.
But, when the component is unmounted it shows the error Cannot call setState on an unmounted component. Previously I handled this error with this.isMounted. Now I was trying to handle it using promise cancelling in componentWillUnmount as mentioned in the React Blog.
this.cancellablePromise = makeCancelable(getSearchResults(word));
this.cancellablePromise.promise
.then(res => {
console.log({ res });
this.setState({ values: res });
})
.catch(err => console.log("error", err));
console.log("in data ", this.cancellablePromise);
}
The cancellablePromise gets assigned after the promise got resolved. So there is a null object in componentWillUnMount for cancellablePromise instance.
Looks like you just need to do the following thing:
myAwesomeMethod = () => {
this.cancellablePromise = makeCancelable(getSearchResults(word));
this.cancellablePromise
.then(...)
.catch(...)
}
componentWillUnmount() {
this.cancellablePromise && this.cancellablePromise.cancel();
}
this.cancellablePromise will be undefined only in the case when you unmount your component before the call of the method with promise call.
The issue is that your handleChange method calls debouncedGetSearchResult, but you are initializing this.cancellablePromise in getSearchResult. This leaves a 500ms window for the unmount to happen before you have initialized this.cancellablePromise.
You need to rework this so that the debouncing is part of the cancelable promise rather than prior to it.

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