I see similar questions in this field but my question is different from these as i don't set state directly from this.state .
my code is
state = {
count: 0};
handleIncrement = product => {
console.log(product);
this.setState({ count: ++this.state.count });
};
as you see i don't set state directly but given me this error at this line
When you do this:
++this.state.count
This tries to mutate the state directly, because you're trying to do this:
this.state.count = this.state.count + 1
which changes the current value of state, but you should do this using setState not directly as the error says.
Do this instead:
this.setState({ count: this.state.count + 1 });
As a best practice, use a callback to avoid a race condition, such as:
this.setState(prev => {
return {
count: prev.count + 1,
};
});
You shouldn't mutate the state directly you can do as follows.
this.setState(prevState => ({
count: prevState.count+1;
}));
You can functionally update the state, therefore it takes the previous value of state, the props you are passing it and calculates the next state like so:
handleIncrement = product => {
console.log(product);
this.setState((state, props) => {
return {
count: state.count + 1
}
});
};
Related
I have this componet with state, here when the user is online it increases the count. I want to change it to functional component with hooks, which I have done below
class App extends React.Component {
state = {
isOnline: true,
count: 1
}
handleOnline = () => {
if (!this.state.isOnline) {
this.setState({
count: this.state.count + 1
}, () => {
this.setState({ isOnline: !this.state.isOnline })
})
} else {
this.setState({ isOnline: !this.state.isOnline })
}
}
render() {
return (
<div className="App">
<h1>online ==> {this.state.isOnline ? 'Online' : 'Offline'}</h1>
<h1>count ==> {this.state.count}</h1>
<button onClick={this.handleOnline}>Toggle</button>
</div>
);
}
}
Here is my conversion to functional component with hooks,
const App = () => {
const [isOnline, setIsOnline] = useState(true)
const [count, setCount] = useState(1)
const handleClick = () => {
if (!isOnline) {
setIsOnline(!isOnline)
setCount(count + 1)
} else {
setIsOnline(!isOnline)
}
}
return (
<div className="App">
<h1>online ==> {isOnline ? 'Online' : 'Offline'}</h1>
< h1 > count ==> {count}</h1>
<button onClick={handleClick}>Toggle</button>
</div>
)
}
In the class based component, I have read not to use setState one after another so I used the callback function in this.setState like this
this.setState({
count: this.state.count + 1
}, () => {
this.setState({ isOnline: !this.state.isOnline })
})
Now, in the functional component I have used setCount and setIsOnline one after another is it good ???
const handleClick = () => {
if (!isOnline) {
setIsOnline(!isOnline)
setCount(count + 1)
} else {
setIsOnline(!isOnline)
}
I have read to use useEffect for callbacks, but all I get is infinite loop. Even though both of my components work and give me the desired result . I wanted to know if i must use useEffect for the callback or if my implementation with hooks in functional component is correct???
This implementation is correct, yes we should not set one state after the other because setState works asynchronously but since you are only setting two states so its fine.
Although you can also keep one state object instead of both separate states i.e
const [state, setState] = useState({ count: 1, isOnline: true });
And then you can set both object keys in a single setState, like:
setState(() => ({
count: 1,
isOnline: false,
}))
Also in the class based approach you have used a callback but you actually don't need that, you can use single setState for setting both states i.e.
this.setState(() => ({
count: this.state.count + 1,
isOnline: !this.state.isOnline ,
}))
Another important note:
Try to use functional set state as I use in examples above, as it reduces the risk of being caught into React state asynchronous issues.
Calling set state one after the other is totally fine and is the correct thing to do here:
const handleClick = () => {
if (!isOnline) {
setIsOnline(!isOnline)
setCount(count + 1)
} else {
setIsOnline(!isOnline)
}
}
State is updated asychronously, which means that your state variables isOnline and count don't actually change until your component re-renders. Calling setCount and setIsOnline doesn't update these variables, but tells React to update on the next render.
This is why you can't do something like this:
const handleClick = () => {
setCount(count + 1)
setCount(count + 1)
}
The count would be incremented by 1, NOT by 2.
Why?
Because the value count has not been updated yet, since we have to wait until re-render before it gets updated. That means count has the same value the entire way through the function - it never changes. So you can call setCount(count + 1) a million times and the value will only ever increment by 1.
This is what people mean when they say you should use the set state callback function.
This is the set state callback function:
const handleClick = () => {
setCount(prev => prev + 1)
setCount(prev => prev + 1)
}
This works as expected, and count will now be incremented by 2.
If we pass a function like this prev => prev + 1 to set state, React will pass the most recent value to the function.
The rule is:
Every time you use the old state value to set the new state value - pass a function to set state.
So although your current implementation does work, you should really be passing a function to set state on count since you are depending on previous state:
const handleClick = () => {
if (!isOnline) {
setIsOnline(!isOnline)
setCount(prev => prev + 1)
} else {
setIsOnline(!isOnline)
}
}
Usually you should do this for your boolean value too, for example:
setIsOnline(prev => !prev)
but since you're using isOnline in your if statement, you shouldn't do that here as the prev value may be different than the value your if uses.
Case1.
handleRemovePlayer = id => {
this.setState(prevState => {
return {
players: prevState.players.filter(player => player.id !== id)
};
});
};
Case2.
// Arrow Func: become Component instance
incrementScore = () => {
this.setState(prevState => ({
score: this.state.score + 1
}));
};
decrementScore = () => {
if (this.state.score > 0) {
this.setState(prevState => ({
score: this.state.score - 1
}));
}
};
In setState(), why case1 cannot use this.players.filter instead of prevState.player? Both case1 and 2 use the same prevState callback.. Can anyone explain precisely regarding prevState?
Thanks in advance!
Currently, setState() is asynchronous inside event handlers.
Let assume that- you updated your state and you want to check state is updated or not.
for that you can use updater(callback) as 2nd argument to check updated state.
like this -
incrementScore = () => {
this.setState(prevState => ({
score: prevState.score + 1
}),()=>{
console.log(this.state.score)
});
};
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 ...for your reference setState in reactjs
SetState is an asynchronous method. So, if there are more than 1 setState execution methods, the second approach may result in the value which we are not interested in. But the first approach will make sure we always get the latest state value.
One should always use prevState instead of this.state.
I am new to React and was learning how setState works. So, as you can see the code below:
class Counter extends React.Component {
constructor(props) {
super(props);
this.handleReset = this.handleReset.bind(this);
this.state = {
count: 5
};
}
handleReset() {
this.setState({count:0})
this.setState({count:this.state.count+1})
}
render() {
return (
<div>
<button onClick={this.handleReset}>reset</button>
</div>
);
}
So, what I expect from the above code is that when I click on button, instead of outputting 1 it adds 1 to the current state of count and shows 6. This is how I assume both setStates work in handleReset method. I do not know why it renders 6 instead of 1
As Rikin explained, setState batches them together and only executes the last one... And yes setState is async and does take a callback so if you wanted your expected result you'd do this instead:
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 5 };
this.handleReset = this.handleReset.bind(this);
}
handleReset() {
this.setState({ count: 0 },
/* here is you callback */
() => this.setState({ count: this.state.count + 1 }))
}
render() {
return (
<div>
<div>{this.state.count}</div>
<button onClick={this.handleReset}>reset</button>
</div>
);
}
}
Hope it helps... Here's a live demo: https://stackblitz.com/edit/react-dbc11s
setState calls are batched together thus only the final one makes the update. To explain that scenario let's use OP's example with some more similar hardcoded updates as:
handleReset() {
this.setState({ count: 0 })
this.setState({ count: 10 })
this.setState({ count: 100 })
this.setState({ count: this.state.count + 1 })
}
What happens above under the hood is you can think of it as:
Object.assign(
previousState, // { count: 5 }
{count: 0}, // { count: 0 }
{count: 10}, // { count: 10 }
{count: 100}, // { count: 100 }
{count: state.count + 1} // { count: 5 + 1}
)
Object.assign overwrites value of the count and thus only last value makes the final update which happens to be 6
In case if you want to use prior state's value, you would have to rely on functional setState method which uses existing state value at the time of execution and then performs update operation on it.
Both scenarios demo here: https://codesandbox.io/s/musing-rhodes-jsf6b
setState can take an updater function as an argument. In that case, the function will be passed the previous state, and you return a change object that will inform the next state.
The documentation even mentions this directly.
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 [in the documentation].
So instead of:
this.setState({ count: this.state.count + 1});
...you can do:
this.setState((prevState) => ({ count: prevState.count + 1 }))
For more information, see also this learn react article.
I think it might be silly question to ask but trust me I am beginner to reactJS . Could someone please explain me why we use prevState in ReactjS . I tried hard to understand but failed .
Here is my code. Please Help me to understand
state = {
placeName : '',
places : []
}
placeSubmitHanlder = () => {
if(this.state.placeName.trim()===''){
return;
}
this.setState(prevState => {
return {
places : prevState.places.concat(prevState.placeName)
};
});
};
prevState is a name that you have given to the argument passed to setState callback function. What it holds is the value of state before the setState was triggered by React; Since setState does batching, its sometimes important to know what the previous state was when you want to update the new state based on the previous state value.
So if multiple setState calls are updating the same state, batching setState calls may lead to incorrect state being set. Consider an example:
state = {
count: 0
}
updateCount = () => {
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
}
In the above code you might expect the value of count to be 4 but it would actually be 1 since the last call to setState will override any previous value during batching. A way to solve this to use functional setState:
updateCount = () => {
this.setState(prevstate => ({ count: prevstate.count + 1}));
this.setState(prevstate => ({ count: prevstate.count + 1}));
this.setState(prevstate => ({ count: prevstate.count + 1}));
this.setState(prevstate => ({ count: prevstate.count + 1}));
}
You use it when you want to override the current state with the last state's parameters.
From React docs :
According to the React docs "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"
Link
Here is a demo with a commented-out code to give you more information: http://codepen.io/PiotrBerebecki/pen/rrGWjm
constructor() {
super();
this.state = {
value: 0
}
}
React docs: https://facebook.github.io/react/docs/reusable-components.html#es6-classes
The [React ES6 class] API is similar to React.createClass with the exception of getInitialState. Instead of providing a separate getInitialState method, you set up your own state property in the constructor.
Where does prevState come from?
The prevState comes from the setState api: https://facebook.github.io/react/docs/component-api.html#setstate
It's also possible to pass a function with the signature function(state, props). This can be useful in some cases when you want to enqueue an atomic update that consults the previous value of state+props before setting any values. For instance, suppose we wanted to increment a value in state:
this.setState(function(previousState, currentProps) {
return {
value: previousState.value + 1
};
});
I know this.state is not supposed to be modified directly, instead setState should be used.
From this I inferred that prevState should be also treated as immutable and instead setState should always look something like this:
this.setState((prevState) => {
// Create a new object instance to return
const state = { ...prevState };
state.counter = state.counter + 1;
return state;
});
Or with deeper nesting:
this.setState((prevState) => {
// Create a new object instance to return
const state = { ...prevState, counter: { ...prevState.counter } };
state.counter.value = state.counter.value + 1;
return state;
});
Or just a partial update like would be with setState({}) where easier and nicer to use:
this.setState((prevState) => ({ counter: prevState.counter + 1 }));
All of the above are obviously correct because they return a new instance, but then I came across this question where the accepted answer encourages mutating prevState without returning a new object instance (notice the code block in the question).
Something like this:
this.setState((prevState) => {
prevState.flag = !prevState.flag;
return prevState;
});
I found this to be a sketchy suggestion so I decided to test if the object instance references of prevState and this.state are the same:
(The JSFiddle)
class Test extends React.Component {
state = { counter: 0 };
onDemonstrateButtonClick = (event) => {
this.setState((prevState) => {
if (prevState === this.state) alert(`uh, yep`);
prevState.counter++;
return prevState;
});
};
render() {
return (
<div>
<button onClick={this.onDemonstrateButtonClick}>Demonstrate</button>
{this.state.counter}
</div>
);
}
}
Whadayaknow, they are! So which is it? Is the answer wrong and should I return a new object instance or return partial update as a new object or can I go wild and mutate the prevState argument directly? And if yes, how is this any different from mutating this.state directly?
Side note: TypeScript React typings do not mark the argument as ReadOnly which only adds to my confusion.
First Point
Is it okay to treat the prevState argument of setState's function as
mutable?
The answer is NO you should never mutate prevState, this is also clearly mentioned in react documentation in setState section
prevState is a reference to the previous state. It should not be
directly mutated. Instead, changes should be represented by building a
new object based on the input from prevState and props.
Second Point:
You tested prevState and this.state and they are the same, well actually they are not.
To figure out why they are actually different we need to know why prevState actually exist, and the answer is that setState function is asynchronous, thats why react js is giving us access to prevState lets check the example below where prevState != this.state
In the example below we will increment counter twice per click, but we will use 2 setState operations each one of them will increment the counter by 1.
Because setState is async you will notice that the second setState operation started before the first setState is finished this is where prevState is useful and here prevState and this.state are not equal.
I commented each line with a number denoting when this line is executed, this should explain why we need prevState and why it is different from this.state.
class App extends React.Component{
constructor(props)
{
super(props);
this.state = {
counter : 1
};
}
increment = () =>{
this.setState((prevState , props) => {
console.log("in first"); //3
console.log(prevState); //3
console.log(this.state); //3
if(prevState == this.state)
{
console.log("in first prevState == this.state");
}
return {
counter : prevState.counter+1
}
} , ()=>{
console.log("done updating first"); //5
});
console.log("after first"); //1
this.setState((prevState, props) => {
console.log("in second"); //4
console.log(this.state); //4
console.log(prevState); //4
if (prevState == this.state) {
console.log("in second prevState == this.state");
}
return {
counter: prevState.counter + 1
}
} , () =>{
console.log("done updating second"); //6
});
console.log("after second"); //2
}
render(){
return (
<div>
<span>{this.state.counter}</span>
<br/>
<button onClick={this.increment} >increment</button>
</div>
)
}
}
The Result from the above code is
"after first"
"after second"
"in first"
▶Object {counter: 1}
▶Object {counter: 1}
"in first prevState == this.state"
"in second"
▶Object {counter: 1}
▶Object {counter: 2}
"done updating first"
"done updating second"
The above code is fully working in this link, you can check the console.log result
https://codesandbox.io/s/k325l485mr
The above example will correctly increment counter twice per click, if you want to break it change return statement in second setState
from
return {
counter: prevState.counter + 1
}
to
return {
counter: this.state.counter + 1
}
and you will find that the result is not correct each click will result in 1 increment which is not correct because we have 2 setState , this is because we didn't use prevState and we used an incorrect this.state
Finally
I believe that the correct way to update the counter is
this.setState((prevState) => ({ counter: prevState.counter + 1 }));