So I have some confusion regarding the async nature of setState in ReactJS.
As per React docs, you shouldn't use this.state inside setState(). But if I have a counter as a state and i want to update it on click like this:
class App extends React.Component {
state = { counter: 0 }
onClick = () => {
this.setState({counter: this.state.counter + 1})
}
render() {
return (
<div>
<div>{this.state.counter}</div>
<p onClick={this.onClick}>Click me</p>
</div>
)
}
}
This works as expected. So why is this code wrong?
UPDATE: I know that setState is async, and it accepts a callback which has previous state as an argument, but I am not sure why I should use it here? I want to refer to the old state inside setState, so why should I use the callback function in this case? Whenever this.setState() is executed, this.state inside it will always refer to the old state, and its value will be changed to the new state only after setState has finished executing, not while it is executing.
You have access to prevState from within your setState call:
this.setState((prevState) => ({
counter: prevState.counter +1
}))
That will allow you to safely increment the current state value.
The React documentation summarises why you cannot rely on this.state to be accurate during update: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
setState accepts a function as a parameter with previous state as argument.
Refacto as follows to avoid competition problems:
onClick = () => {
this.setState(prevState => ({counter: prevState.counter + 1}))
}
Related
I am following React Doc. In 'Handling Events' section, the below code segment is there.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
in handleClick() function, how state is available for it to access? Why not this.state?
This is how this.setState works, its an updater callback passed for this.setState(), as per react documentation:
Passing an update function allows you to access the current state
value inside the updater. Since setState calls are batched, this lets
you chain updates and ensure they build on top of each other instead
of conflicting
more information can be found here as well.
Because it's a function parameter. In this code:
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
This part, is an arrow function, which takes previous state of the component, as a parameter:
state => ({
isToggleOn: !state.isToggleOn
})
And returns a new state, which triggers re-render and so on.
Why not this.state?
The rule of thumb: If your next state depends on previous state, you must use this approach to update your state. So you don't run into a race condition with this.setState calls because is asynchronous function.
Hope it helps :)
constructor(props) {
super(props);
this.state = {
active: false,
showSideBar: false,
className: ""
}
}
componentDidMount() {
if (this.props.overlay) {
this.setState({
className: "wrapper_overlay"
});
alert(this.state.className);
}
else if (this.props.SideBarWithIcon) {
this.setState({
className: "wrapper_clopsed"
});
}
}
I am updating my state with the help of the props but the component is getting props but state is not updating
setState is asynchronous. Just alert in a callback to the method instead.
if (this.props.overlay) {
this.setState(
{ className: "wrapper_overlay" },
() => alert(this.state.className);
);
}
Note: you can, and should, also use shouldComponentUpdate to check for when a setState call completes
Since setstate is async in nature so you maynot see updated state in alert.
You can use that in callback which will be called once the setstate is done. like this
componentDidMount() {
if (this.props.overlay) {
this.setState({
className: "wrapper_overlay"
}, ()=> {alert(this.state.className);});
}
State updates may be asynchronous
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.
// Wrong
this.setState({
className: "wrapper_overlay"
});
To fix it, use a second form of setState() that accepts a function
rather than an object.
// Correct
this.setState((state, props) => ({
className: "wrapper_overlay"
}));
or just
this.setState(() => ({
className: "wrapper_overlay"
}));
In React.js, running program thread does not wait for setting state and continues its execution, until and unless operation defined in setState() callback.
Your state is getting set but as your alert() is after setstate i.e. not in callback of setState() that's why its getting previous value because at the time state is setting new value, thread is not waiting but executing the next instruction which is alert().
if (this.props.overlay) {
this.setState({ className: "wrapper_overlay" }, () => alert(this.state.className););
}
Hope this works for you.
Just use the callback method to update the state, as
setState() might work asynchronously.
this.setState(() => {
className: "wrapper_clopsed"
});
This question already has answers here:
Whats the best way to update an object in an array in ReactJS?
(4 answers)
Closed 4 years ago.
I am using React components which look like this (a simplified version of the components I used, below).
My question is: how to make the same but using this.setState?
The below code works, but I am mutating the state directly, and I am receiving the following warning:
Do not mutate state directly. Use setState()
class App extends Component {
constructor(props) {
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
},{
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.state.playerState[index].hp = this.state.playerState[index].hp - 1
this.forceUpdate();
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}
When rendered, it looks like this:
If you want to modify an existing value in the state, you should never get the value directly from the state and update the state object, but rather use the updater function in setState so you can guarantee the state values are the ones you need at the time of updating the state. This is just how React's state works and it's a very common React mistake.
From the official 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. 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.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
Both state and props received by the updater function are guaranteed
to be up-to-date. The output of the updater is shallowly merged with
state.
So you must get the value exactly when you want to update the component inside the setState function using the first argument of the updater function.
lowerPlayerHealth = (index) => () => {
// use setState rather than mutating the state directly
this.setState((state, props) => {
// state here is the current state. Use it to update the current value found in state and ensure that it will be set correctly
return (state); // update state ensuring the correct values
});
}
Solution
To lower a value found in state:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
}, {
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.setState((state, props) => {
state.playerState[index].hp -=1; //update the current value found in state
return (state); // update state ensuring the correct values
});
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}
You've answered your own question: don't mutate state. Also, best practice suggests using the function version of setState.
Since playerState is an array, use Array.map to create a new array containing the same objects, replacing only the one you want to change:
lowerPlayerHealth = (indexToUpdate) => () => {
this.setState(state => ({
...state,
playerState: state.playerState.map(
(item, index) => index === indexToUpdate
? {
...item,
hp: item.hp - 1
}
: oldItem
)
}));
}
If you made playerState an object instead of an array, you can make it tidier:
lowerPlayerHealth = (indexToUpdate) => () => {
this.setState(state => ({
...state,
playerState: {
...state.playerState,
[indexToUpdate]: {
...state.playerState[indexToUpdate],
hp: state.playerState[idToindexToUpdatepdate].hp - 1
}
}
}));
}
Ok, i'll try and make this quick because it SHOULD be an easy fix...
I've read a bunch of similar questions, and the answer seems to be quite obvious. Nothing I would ever have to look up in the first place! But... I am having an error that I cannot fathom how to fix or why its happening.
As follows:
class NightlifeTypes extends Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: event.target.checked});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
More code surrounds this but this is where my problem lies. Should work, right?
I've also tried this:
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: !this.state.barClubLounge});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
So I have those two console.log()'s, both should be the same. I'm literally setting the state to be the same as the event.target.checked in the line above it!
But it always returns the opposite of what it should.
Same goes for when I use !this.state.barClubLounge; If it starts false, on my first click it remains false, even though whether the checkbox is checked or not is based off of the state!!
It's a crazy paradox and I have no idea whats going on, please help!
Reason is setState is asynchronous, you can't expect the updated state value just after the setState, if you want to check the value use a callback method. Pass a method as callback that will be get executed after the setState complete its task.
Why setState is asynchronous ?
This is because setState alters the state and causes re rendering. 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.
From Doc:
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.
Using callback method with setState:
To check the updated state value just after the setState, use a callback method like this:
setState({ key: value }, () => {
console.log('updated state value', this.state.key)
})
Check this:
class NightlifeTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
}
handleOnChange = (event) => { // Arrow function binds `this`
let value = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: value}, () => { //here
console.log(value);
console.log(this.state.barClubLounge);
//both will print same value
});
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
}
ReactDOM.render(<NightlifeTypes/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
Since setState is a async function. That means after calling setState state variable does not immediately change. So if you want to perform other actions immediately after changing the state you should use callback method of setstate inside your setState update function.
handleOnChange = (event) => {
let inputState = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: inputState}, () => { //here
console.log(this.state.barClubLounge);
//here you can call other functions which use this state
variable //
});
}
}
This is by-design due to performance considerations. setState in React is a function guaranteed to re-render Component, which is a costly CPU process. As such, its designers wanted to optimize by gathering multiple rendering actions into one, hence setState is asynchronous.
I want to make a loading indicator on user login, but for some reason the JSX conditional element does not update:
class LoginPage extends Component {
constructor (props) {
super(props)
this.state = {
waitingOnLogin: false,
...
}
this.handleLogin = this.handleLogin.bind(this)
}
handleLogin (event) {
event.preventDefault()
this.state.waitingOnLogin = true
this.props.userActions.login(this.state.email, this.state.password)
}
render() {
return (
<div className='up'>
<form onSubmit={e => this.handleLogin(e)}> ... </form>
{this.state.waitingOnLogin && <Spinner message='Logging you in'/>} // This does not appear
</div>
)
}
}
Why does the waitingOnLogin is being ignored by the JSX?
Don't mutate state directly use setState. setState calls for rerender and hence after that your change will reflect but with direct assignment no rerender occurs and thus no change is reflected. Also you should always use setState to change state
handleLogin (event) {
event.preventDefault()
this.setState({waitingOnLogin:true});
this.props.userActions.login(this.state.email, this.state.password)
}
Always use setState to update the state value, never mutate the state values directly, Use this:
handleLogin (event) {
event.preventDefault()
this.setState({ waitingOnLogin: true });
this.props.userActions.login(this.state.email, this.state.password)
}
As per DOC:
Never mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
Check the details about setState.