Why does state not update as expected? - javascript

I am currently building a TicTacToe game and would like to store my current player in state as currentPlayer. After one player moves, I update currentPlayer to the opposite player. However, when I try to log the new state to the console, it's not producing the value of the updated state.
Here is my code:
state = {
currentPlayer: 'X',
}
// This function is triggered by an onClick attribute.
// It first finds the html element and renders the currentPlayer value from state.
// It then switchs the value of currentPlayer from X to O and calls setState to update the state.
// Why does the console.log not show the updated state value?
userDidMove = () => {
document.getElementById('cell').innerHTML = this.state.currentPlayer
let nextPlayer = this.state.currentPlayer === 'X' ? 'O' : 'X'
this.setState({
currentPlayer: nextPlayer,
})
console.log ('userDidMove changed state with ',this.state.currentPlayer)
}
Any help figuring out how to get this function to return the updated state value would be great!

State changes are asynchronous. When your new state is dependent on the previous state, use the state updater function instead.
When the state changes are committed you can use the callback which will have the updated state.
this.setState((previousState) => {
const nextPlayer = previousState.currentPlayer === 'X' ? 'O' : 'X';
return {
currentPlayer: nextPlayer
}
}, () => {
// your updated state related code here
console.log('userDidMove changed state with ', this.state.currentPlayer)
});
this.setState(updatedFunc, callback);

setState is asynchronous, so the state isn’t updated immediately. You can pass a callback as the second argument to setState that will only be called when state has been updated:
this.setState(
{ currentPlayer: nextPlayer },
() => console.log(`userDidMove changed state with ${this.state.currentPlayer}`)
);
setState (React Docs):
setState(updater[, callback]) OR setState(stateChange[, callback])
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.
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.
NOTE: I suggest observing state using the React Dev Tools, instead of logging it.
UPDATE: This answer initially stated, incorrectly, that setState returned a promise and suggested that you could chain .then() that would be called once state was updated. I've since corrected the answer, with inspiration from #Sushanth's answer.

State changes are asynchronous. so use a function instead and the second parameter of setState function you may call the callback function to console or something else to do.
this.setState(() => ({currentPlayer: nextPlayer}), () => {
console.log('state', this.state.currentPlayer);
})

Related

ReactJS - setState does not update property [duplicate]

I have just found that in react this.setState() function in any component is asynchronous or is called after the completion of the function that it was called in.
Now I searched and found this blog (setState() State Mutation Operation May Be Synchronous In ReactJS)
Here he found that setState is async(called when stack is empty) or sync(called as soon as called) depending on how the change of state was triggered.
Now these two things are hard to digest
In the blog the setState function is called inside a function updateState, but what triggered the updateState function is not something that a called function would know about.
Why would they make setState async as JS is single threaded language and this setState is not a WebAPI or server call so has to be done on JS's thread only. Are they doing this so that Re-Rendering does not stop all the event listeners and stuff, or there is some other design issue.
You can call a function after the state value has updated:
this.setState({foo: 'bar'}, () => {
// Do something here.
});
Also, if you have lots of states to update at once, group them all within the same setState:
Instead of:
this.setState({foo: "one"}, () => {
this.setState({bar: "two"});
});
Just do this:
this.setState({
foo: "one",
bar: "two"
});
1) setState actions are asynchronous and are batched for performance gains. This is explained in the documentation of setState.
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.
2) Why would they make setState async as JS is a single threaded language and this setState is not a WebAPI or server call?
This is because setState alters the state and causes rerendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better UI experience and performance.
I know this question is old, but it has been causing a lot of confusion for many reactjs users for a long time, including me.
Recently Dan Abramov (from the react team) just wrote up a great explanation as to why the nature of setState is async:
https://github.com/facebook/react/issues/11527#issuecomment-360199710
setState is meant to be asynchronous, and there are a few really good reasons for that in the linked explanation by Dan Abramov. This doesn't mean it will always be asynchronous - it mainly means that you just can't depend on it being synchronous. ReactJS takes into consideration many variables in the scenario that you're changing the state in, to decide when the state should actually be updated and your component rerendered.
A simple example to demonstrate this, is that if you call setState as a reaction to a user action, then the state will probably be updated immediately (although, again, you can't count on it), so the user won't feel any delay, but if you call setState in reaction to an ajax call response or some other event that isn't triggered by the user, then the state might be updated with a slight delay, since the user won't really feel this delay, and it will improve performance by waiting to batch multiple state updates together and rerender the DOM fewer times.
Good article here https://github.com/vasanthk/react-bits/blob/master/patterns/27.passing-function-to-setState.md
// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3
Solution
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}));
or pass callback this.setState ({.....},callback)
https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82
https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b
You can use the following wrap to make sync call
this.setState((state =>{
return{
something
}
})
Yes, setState() is asynchronous.
From the link: https://reactjs.org/docs/react-component.html#setstate
React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component.
Think of setState() as a request rather than an immediate command to update the component.
Because they think
From the link: https://github.com/facebook/react/issues/11527#issuecomment-360199710
... we agree that setState() re-rendering synchronously would be inefficient in many cases
Asynchronous setState() makes life very difficult for those getting started and even experienced unfortunately:
- unexpected rendering issues: delayed rendering or no rendering (based on program logic)
- passing parameters is a big deal
among other issues.
Below example helped:
// call doMyTask1 - here we set state
// then after state is updated...
// call to doMyTask2 to proceed further in program
constructor(props) {
// ..
// This binding is necessary to make `this` work in the callback
this.doMyTask1 = this.doMyTask1.bind(this);
this.doMyTask2 = this.doMyTask2.bind(this);
}
function doMyTask1(myparam1) {
// ..
this.setState(
{
mystate1: 'myvalue1',
mystate2: 'myvalue2'
// ...
},
() => {
this.doMyTask2(myparam1);
}
);
}
function doMyTask2(myparam2) {
// ..
}
Hope that helps.
Imagine incrementing a counter in some component:
class SomeComponent extends Component{
state = {
updatedByDiv: '',
updatedByBtn: '',
counter: 0
}
divCountHandler = () => {
this.setState({
updatedByDiv: 'Div',
counter: this.state.counter + 1
});
console.log('divCountHandler executed');
}
btnCountHandler = () => {
this.setState({
updatedByBtn: 'Button',
counter: this.state.counter + 1
});
console.log('btnCountHandler executed');
}
...
...
render(){
return (
...
// a parent div
<div onClick={this.divCountHandler}>
// a child button
<button onClick={this.btnCountHandler}>Increment Count</button>
</div>
...
)
}
}
There is a count handler attached to both the parent and the child components. This is done purposely so we can execute the setState() twice within the same click event bubbling context, but from within 2 different handlers.
As we would imagine, a single click event on the button would now trigger both these handlers since the event bubbles from target to the outermost container during the bubbling phase.
Therefore the btnCountHandler() executes first, expected to increment the count to 1 and then the divCountHandler() executes, expected to increment the count to 2.
However the count only increments to 1 as you can inspect in React Developer tools.
This proves that react
queues all the setState calls
comes back to this queue after executing the last method in the context(the divCountHandler in this case)
merges all the object mutations happening within multiple setState calls in the same context(all method calls within a single event phase is same context for e.g.) into one single object mutation syntax (merging makes sense because this is why we can update the state properties independently in setState() in the first place)
and passes it into one single setState() to prevent re-rendering due to multiple setState() calls (this is a very primitive description of batching).
Resultant code run by react:
this.setState({
updatedByDiv: 'Div',
updatedByBtn: 'Button',
counter: this.state.counter + 1
})
To stop this behaviour, instead of passing objects as arguments to the setState method, callbacks are passed.
divCountHandler = () => {
this.setState((prevState, props) => {
return {
updatedByDiv: 'Div',
counter: prevState.counter + 1
};
});
console.log('divCountHandler executed');
}
btnCountHandler = () => {
this.setState((prevState, props) => {
return {
updatedByBtn: 'Button',
counter: prevState.counter + 1
};
});
console.log('btnCountHandler executed');
}
After the last method finishes execution and when react returns to process the setState queue, it simply calls the callback for each setState queued, passing in the previous component state.
This way react ensures that the last callback in the queue gets to update the state that all of its previous counterparts have laid hands on.
setState is asynchronous. You can see in this documentation by Reactjs
https://reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-valuejs
https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous
React intentionally “waits” until all components call setState() in their event handlers before starting to re-render. This boosts performance by avoiding unnecessary re-renders.
However, you might still be wondering why React doesn’t just update this.state immediately without re-rendering.
The reason is this would break the consistency between props and state, causing issues that are very hard to debug.
You can still perform functions if it is dependent on the change of the state value:
Option 1:
Using callback function with setState
this.setState({
value: newValue
},()=>{
// It is an callback function.
// Here you can access the update value
console.log(this.state.value)
})
Option 2: using componentDidUpdate
This function will be called whenever the state of that particular class changes.
componentDidUpdate(prevProps, prevState){
//Here you can check if value of your desired variable is same or not.
if(this.state.value !== prevState.value){
// this part will execute if your desired variable updates
}
}

Does render get called immediately after setState?

I have the following function:
onSelectDepartment = (evt) => {
const department = evt.target.value;
const course = null;
this.setState({ department, course });
this.props.onChange({ name: 'department', value: department });
this.props.onChange({ name: 'course', value: course });
if (department) this.fetch(department);
};
The question is, after the setState function get called, the render function on the component will be executed immediately or after function call is finished?
render function on the component will be executed immediately or after
function call is finished?
No one can take the guarantee of when that render function will get called, because setState is async, when we call setState means we ask react to update the ui with new state values (request to call the render method), but exactly at what time that will happen, we never know.
Have a look what react doc says about setState:
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.
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.
Check this answer for more details about async behavior of setState: Why calling setState method doesn't mutate the state immediately?
If you are looking execute some piece of code only once the setState is completed you can use below format.
this.setState({
flag: true
}, ()=> {
// any code you want to execute only after the newState has taken effect.
})
This is the way to make sure your desired piece of code only runs on the new state.

How to handle asynchronous calls to setState in React?

I have a method that toggles a boolean value in state by copying the value then updating state:
toggleSelected = () => {
let selected = this.state.lists.selected;
selected = !selected;
this.setState({
// update state
});
};
I have another method, fired by an onClick handler, that calls toggleSelected twice:
switchList = (listName) => {
const currList = this.getCurrentList();
if(listName === currList.name) return;
this.toggleSelected(listName);
this.toggleSelected(currList);
};
However it appears that the state doesn't finish getting set from the first call by the time the second call runs; if I set a timeout on the second call, it works fine.
What's the correct way to do this?
An alternative to what #SLaks suggested, useful sometimes, is using the setState(new_state, callback) method. The callback will be run once the state is updated and "visible".
In general, setState will not change the state immediately so that it is visible in this.state. From the docs
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.
As noted in the React State/Lifecycle docs, the correct way to update state based on previous state is by passing a function into the setState() call, eg:
toggleSelected() {
this.setState((prevState, props) => {
return {
lists: {
selected : ! prevState.lists.selected
}
};
});
}
This is why you should always pass a lambda to setState(), so that it will give you the actual current state:
this.setState(state => ({ ..., selected: !state.lists.selected }))

React setState() guarantees

In the documentation for setState it has this to say:
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.
So I understand if you have an eventhandler like:
handleClick() {
this.stuff1();
this.stuff2();
}
stuff1() {
this.setState({...});
}
stuff2() {
doSomethingWithState(this.state);
this.setState({...});
}
then in the stuff2 method you might not (or is this guaranteed you won't..) see the updates to state from stuff1. React provides a way of 'safely' accessing the previous state by supplying a function to setState() instead of an object. But presumably you could work around this by keeping track of the state yourself as you handle the event and call setState() once at the end of your method. So maybe there is another reason option for supplying a function to setState(). What if other changes to the state could have been batched before the handleClick method is called?
For example if handleClick() is called twice then am I guaranteed to see the state changes from the first call to handleClick() in the second call to handleClick()? or are there other ways the state could be dirty before my event handler has been called?
Anytime you have a state that is derived from the previous state you can run into an issue of them being out of sync if you are not using the callback function vs passing in an object. In your last example you would only be guaranteed if you used the function instead. If you are relying on this.state you could easily run into a situation where handleClick is called once, the state transition gets queued up to be resolved later, you the have handleClick called again and the first state change is still pending and in queue so when you call this.state it will have the same state available to it as the first handleClick had which wouldn't be what you want.
this would be an example of using the callback function, assuming that doSomethingWithState returns the updated state object and is non mutative of course.
stuff2() {
this.setState((state) => {
const updatedState = doSomethingWithState(state);
return state;
})
}
this is a great article on using the function vs setState and also includes a codepen example demonstrating the problem. https://medium.com/#shopsifter/using-a-function-in-setstate-instead-of-an-object-1f5cfd6e55d1#.gm2t01g70
If i understood you right, then the answer is no, your not guaranteed, as setState is an asynchronous operation - so it's kinda equivalent to the first 'problem' you mentioned. You can provide a callback function to the setState method that will be fired as the setState method finishes. Then, in the callback scope, you are guaranteed that the current state is updated and also provided with the old state for comparisons and other stuff....
You can provide callback as said. Or since you want first function to get executed in handleClick(), what you can do is you can call the second function after setting the state in the first function. i.e.,
handleClick() {
this.stuff1();
}
stuff1() {
this.setState({...},
() => {
this.stuff2();
});
}
stuff2() {
doSomethingWithState(this.state);
this.setState({...});
}
I think this will provide the solution for the problem which is raised. Since we can't guarantee synchronous operation of calls to setState as it is an asynchronous operation.

ReactJS setState only works when nested inside setState

The issue:
When I use this.setState and I output the state in the callback, it doesn't change at all but when I nest the setstate inside a setstate it will then work correctly.
Example:
This doesn't work -
this.setState({
data: newData
});
This does work -
this.setState({
data: newData
}, () => {
this.setState({
data: newData
});
});
Does this have something to do with the way react batches state updates?
This is the actual code in which the setstate doesn't work unless I nest it (I've tried commenting everything out in this function and using setState to set coursePage to null but it doesn't work unless it's nested):
cancelCPIndexChange(index){
let temp = this.state.coursePages;
this.hideEditingCoursePage(index);
let canceledIndex = temp[index];
temp = temp.slice(0, index).concat(temp.slice(index+1));
temp = temp.slice(0, parseInt(canceledIndex.course_pageindex)-1).concat(canceledIndex).concat(temp.slice(parseInt(canceledIndex.course_pageindex)-1));
this.setState({
coursePages: temp
}, () => {this.setState({
coursePages: temp
});
});
}
This is another function on the same level as cancelCPIndexChanges that is able to modify the state of coursePages:
showEditingCoursePage(index){
let temp = this.state.coursePages;
temp[index].editingCoursePage = true;
this.setState({
coursePages: temp
});
}
These functions are in course.js. Both these functions are passed down to CoursePages.js and then to CoursePage.js.
According to: https://facebook.github.io/react/docs/component-api.html
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.
(I've noticed this myself in the past)
I'm not 100% certain, but I'd guess that the second setState in your callback function is "flushing" the pending state transition before creating the second one.
I'm not clear on where you want to consume the new value of state? It ought to be in the render method where you can be sure it's been updated (As the state transition triggers the render). If you want to use it immediately after the setState you still have the reference to the value, so can use that directly.
As said setState behaves asynchronously. There are two ways you can access the newly updated state value before render
Using a call back function for setState method
Use componentDidUpdate method. Using nextState param you can access the latest state. But make sure you should use setState method inside that

Categories