I have used the following pattern in previous ReactJS projects - A LOT!
componentDidMount() {
var promises = [];
var promise1 = this.AsyncFunc1().then(res => {
this.setState({some_state1: res.data.results});
}).catch(error => {
//deal with error
});
var promise2 = this.AsyncFunc2().then(res => {
this.setState({some_state2: res.data.results});
}).catch(error => {
//deal with error
});
promises.push(promise1);
promises.push(promise2);
Promise.all(promises).then(() => {
// Use the state variables
}).catch(error => {
// deal with error
});
}
I understand that state is set asynchronously and not available right away - I avoided accessing the state immediately following the setting of that state in the then statements of the async functions - but I have consistently accessed the state variable values in the promise resolution section in previous projects - but in this new project I'm working on it is failing off and on because the state variable values aren't consistently available. I've resorted to setting temp variables to the res.data.results values and using those in the Promise.all section.
My previous projects have been in production for 2+ years without issues (that I'm aware of) - do I need to go back and rework that code? Did I make a bad assumption that the state variable values would be available in the Promise.all section?
I appreciate the help!
Yes, you made an incorrect assumption; your promise might be called before the state is available. From setState docs, emphasis mine:
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
...
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
There's also a FAQ entry about your kind of case, emphasis again mine:
Why is setState giving me the wrong value?
In React, both this.props and this.state represent the rendered values, i.e. what’s currently on the screen.
Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState. Pass an updater function instead of an object if you need to compute values based on the current state (see below for details).
As in the second quote, the state variables won't be updated until after the render happens, and even though you're not immediately reading state after calling setState, it's still likely that your Promise.all will fire before the this.state is updated—and React absolutely reserves the right to do so. Auditing your previous use of this pattern is probably a good idea.
Some options for you if you really want the render to happen first:
Use componentDidUpdate and check that both state variables are set, which the FAQ recommends.
Use the second setState parameter, updater, which will call you back after the setState takes effect. This is not recommended compared to componentDidUpdate.
With hooks, use useEffect. You can pass your some_state1 and some_state2 in the second argument to useEffect to only take effect when the values change.
Within your AsyncFunc then handlers, wrap your code in flushSync. The docs heavily recommend against this option: flushSync is not as specific as the other options above, but for a stopgap measure on old code it might be effective for you.
If you don't need the UI to update before your Promise.all() handler runs, you can save the resolved values to local variables, or you can make sure to return the values from your AsyncFunc then handlers. If your // deal with error handlers provide fallback values, you can return those from your catch handlers there too; conversely, if you don't want your Promise.all() handler to run if either of those calls fail, you should ensure that they re-throw the exceptions from within the catch block so the promises you save stay rejected.
(Of course, you could also move both of your setState calls to the Promise.all handler so you don't even try to call setState before both values are there, but I presume you want the partial UI update that comes from each individual AsyncFunc.)
Related
I am beginner in react and I am learning about lifecycle hooks. I came across some articles stating don't call setState synchronously inside componentDidMount. But we can setState in then block of promise.
The reason mentioned was setState will cause re-render of the component, which will affect performance.
My question is, even if I write setState inside then block of promise, re-render will be there anyways, also setState is asynchronous so it won't be called immediately. So why its not recommended to call setState inside componentDidMount synchronously, And how does writing inside then block of promise solves the problem if there are any.
PS: I have researching on SO for quite a while, but couldn't find answers that says don't call setState synchronously, I found some related answers, but didn't answer my question, and I couldn't get them because they were totally related to some scenario.
React setState calls are asynchronous. React decides when to do it efficiently. For example, multiple setState calls are batched together into one single update. The way to get around this is using setStates's 2nd parameter which is a callback that will execute once setState is completed and the component re-rendered.
handleThis = () => {
this.setState({someVar: someVal}, funcToCallimmediately )
}
To test, console.log the state variable just after setState() and write another console.log inside the "funcToCallimmediately" function.
📝 Take note that if you need to set the state based on the previous state you should use another method. First, just know that setState's 1st parameter can be an object and can also be a function. So you do it like this...
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
To understand more, read more about setState() here. You'll get what you need.
componentWillMount() {
let dID=this.props.match.params.datumID;
this.setState({datumID: dID});
console.log(dID);
console.log(this.state.datumID);
}
I'm just trying use setState() method.
But the problem is it is not working.
Here is what I get as output:
First console.log() : 66
Second console.log(): null
this.setState accepts a second argument as callback which is fired after the state is successfully changed.
componentWillMount() {
let dID=this.props.match.params.datumID;
console.log(dID);
this.setState({datumID: dID},()=>console.log(this.state.datumID));
}
side note : Move your code to componentDidMount.
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method
will not trigger a re-render. Avoid introducing any side-effects or
subscriptions in this method.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
If you need to set the state based on the previous state,
read about the updater argument below.
this.setState((prevState, props) => {
return {datumID: props.match.params.datumID};
});
Because SetState() is an asynchronous function, use below code
componentWillMount() {
let dID=this.props.match.params.datumID;
this.setState({datumID: dID},
function(){
console.log(this.state.datumID)});
console.log(dID);
}
and if possible try to avoid compenentWillMount since it would be deprecated soon
setState() being an asynchronous function it is in progress of updating the state and javascript being single-threaded language it will execute the next line of code. So if you want to see the state value set you have to do something like this.
componentDidMount=()=> {
let dID=this.props.match.params.datumID;
this.setState({
datumID: dID
},()=>{
console.log(dID);
console.log(this.state.datumID);
});
}
Also I would recommend you to use componentDidMount as willMount won't work in the future as it is deprecated
this.setState is an asynchronous function that takes time and passes the newly assigned
state value over all of the React's life-cycle functions in order to update a state and trigger the re rendering of a component. Your first console logs out the value that is assigned to set state and the next console logs out the value that is currently residing in the state this.state.datumID. if you need to perform operations after setting state you can pass a callback that is triggered after a state is successfully updated.
this.setState({datumID: dID}, () => console.log(this.state.datumID) ); // this will log the updated value
Another thing I want to point out is componentWillMount will wont work in the future release. If you want to do something after DOM is rendered, use componentDidMount or you can perform pre-render task in the constructor of your Class Component.
I see that setState is async. So if I were to call:
this.setState({ variable: true });
and immediately call:
this.setState({ variable: false });
before render is called, am I guaranteed that 'variable' will be false when React is finished processing? In other words, are the async operations sync? Will render be called twice, or will 'variable' be overwritten and render called once with variable=false?
From the react docs for setState:
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
So your logic should not rely on setState execution time. If you like to control render you should consider using shouldComponentUpdate(nextProps, nextState).
Use shouldComponentUpdate() to let React know if a component’s
output is not affected by the current change in state or props. The
default behavior is to re-render on every state change, and in the
vast majority of cases you should rely on the default behavior.
It is best not to rely upon this behavior. It will only work sometimes and not others.
To reliably set multiple state properties, gather all the updates and set them in a single call:
const changes = {};
if (some logic) { changes.variable = true; }
if (some more logic) { changes.variable = false; }
this.setState(changes);
Great question! Made me curious as well, so I whipped up this JSFiddle demonstrating this behavior.
React calls render synchronously after your event handler.
Meaning that because your are doing two updates in the same function, it won't re-render until after the function completes. That means that in each render, this.state.variable will be false.
In React when you want to update state based on a previous value of the state, due to some asynchronous qualities about the setState method, you need to pass in a function as the first param so that at the time state is actually being updated setState has access to the most up to date version of the state. e.g.
const setStateCallback = prevState => {
return {myInteger: prevState.myInteger + this.props.step};
}
this.setState(setStateCallback);
My question is what is the point of the props argument in this function if state is the only piece of data is potentially out of date. Why not just use
const setStateCallback = prevState => {
return {myInteger: prevState.myInteger + this.props.step};
}
this.setState(setStateCallback);
The way I assume setState is working is that setStateCallback gets stored and will be invoked once React is ready to process all of the setState calls. At that time, yes I would need to have access to the state updates that have happened during with other setState calls in the process. However, when setStateCallback is called any updates that were made to this.props.step in the meantime would still be available on this.props.step because I am referencing the this.props object. If I did this
const step = this.props.step
const setStateCallback = prevState => {
return {myInteger: prevState.myInteger + step};
}
this.setState(setStateCallback);
This is a problem because the value of step (assuming this is a Number or String) is no longer being referenced off the props object and could be out of date.
So given my assumptions are correct, accessing the props argument given to the callback setStateCallback seems unnecessary.
UPDATE
As pointed out by Brad Bumbalough, and a little extra evidence by myself below, the props arg is indeed necessary.
The implications of this finding mean you must be careful whenever accessing this.state or this.props within your custom class methods. For example, for any custom class methods called within a setState callback you need to make sure that any references within those methods or references within subsequent class methods made from that call take state and props as arguments and don't refer to the this.props or this.state.
I think their point is that everything is asynchronous. So, if you are expressly depending on props for your state change, you can't be 100% certain that this.props hasn't changed after the setState call is first queued.
A possible lifecycle of when this would be a problem could be...
Initial render.
Async call to get data from server.
User interaction triggers a setState call that depends on current props (prevState.myInteger + props.step). This will be evaluated immediately with current state/props, and the result gets stored as the new value for the state attribute.
setState call is queued.
Async call returns and updates parent component/store, trigging props to change.
setState call is processed and applied, but the props used to generate the stored state data is now out of sync with the current props the user can see, leading to confusion, chaos, and WW3. Ok, I might have exaggerated there.
The key is that it might change. 99% of the time you probably won't need to drill this specific into the call. But the call is there, if you so need it.
In case anyone else was looking, here's the link to the docs on this.
React may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument.
I was working with React JS and I was wondering the difference between calling setState() twice to set two different state variables like this:
this.setState({fruit1: “apple”});
this.setState({fruit2: “mango”});
AND
calling setState() once and passing both state variables as JSON like this:
this.setState({fruit1: “apple”, fruit2: “mango”});
Also what is the better practice: putting state variable names in double quotes like this: this.setState({"fruit1": “apple”}) or simply ignoring the quotes?
From React documentation:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate(). If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
So, use this.setState({fruit1: “apple”, fruit2: “mango”});
For the second question you can look here and here
Inside a React event handler (that is, a function called from a React-based onChange={...} property and the like), multiple calls to setState are batched and the component is only re-rendered a single time. So, there's no difference between
handleClick: function() {
this.setState({fruit1: "apple"});
this.setState({fruit2: "mango"});
}
and
handleClick: function() {
this.setState({fruit1: "apple", fruit2: "mango"});
}
However, outside of the React event system, the two calls to setState will not be merged unless you wrap them in a function passed to React.addons.batchedUpdates. This is normally not something you have to worry about, but may become an issue if you start setting state in response to asynchronous events (e.g. timers, promises, callbacks, etc). For that reason, I would generally recommend the second form (with the two objects merged into one) over the first.
Here is a detailed explanation of React States and setState() method.
States (and props) are only two reasons why React re-renders/recalculate the DOM. It means if we change State, we are telling react to change the related behaviour of our app.
State is a Java-Script object with key:value pair. Now we may have many States (many key:value pairs) and let's say at certain point only one state is changing. in that case we may use this.setState() method to change only that specific state.
state = { fruit1: 'mango', fruit2: 'apple' }
let's say we want to update fruit1: 'watermelon'.
Here we can say:
this.setState( {fruit1: 'watermelon'} );
Here, we did not say anything about second state (fruit2), so react will merge changed state (fruit1) with old state (fruit2).
While, we can also say:
this.setState( {fruit1: 'watermelon' ,fruit2:'apple'} );
But it is not necessary.
Correct and Re-commanded way of setting/changing State:
From React Official docs:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
So here is a better way to do it:
If we are updating the counter or calculating something:
this.setState((prevState,props) => {
return {
{ counter: prevState.counter+1; }
}