I'm new to React, I was told by a book that it is incorrect to use setState method as the example below:
...
constructor(props) {
super(props);
this.state = {
counter: 0,
hasButtonBeenClicked: false
}
}
render() {
return (
<button onClick={ this.handleClick }>
Click
</button>
)
}
handleClick = () => {
this.setState({ counter: this.state.counter + 1 });
this.setState({ hasButtonBeenClicked: this.state.counter > 0 });
}
...
becuase React performs changes to state data asynchronously and may choose to group together several updates to improve performance.
I get the point, but since React performs changes asynchronously . there could be times and chances that the first setState method will get called first before the second setState method, it is just a matter of probability. But no matter how many times I tried, the second setState method always get called first, why?
Despite the fact that they're performed asynchronously, you're unlikely to see variance when testing from the same browser, and / or on the same hardware. Different browsers optimise instruction sets in slightly different ways, which is your best bet for examining performance (and why testing on all major desktop browsers is still recommended despite commercially Chrome often being "good enough").
The best way to do this - assuming you want them to fire in a specific order - is to chain the setState call as follows:
handleClick = () => {
this.setState({ counter: this.state.counter + 1 }, () => {
this.setState({ hasButtonBeenClicked: this.state.counter > 0 })
});
}
But I would recommend a different design regardless - tying the hasButtonBeenClicked logic to the counter is going to introduce problems down the line.
Don't reference this.state. Use callback variant of setState, get current state from there. That'll explicitly guard you against the "changed the state, but the state hasn't changed yet" situations.
And 2. Simply put those two updates into one run of setState:
this.setState((prevState) => ({
counter: prevState.counter + 1,
hasButtonBeenClicked: prevState.counter + 1 > 0 }));
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"
});
Or in your case:
handleClick = () => {
this.setState({
counter: this.state.counter + 1,
hasButtonBeenClicked: this.state.counter > 0
});
}
You are right, React performs changes to state data asynchronously to improve performance.
In your case, you should write code like this:
handleClick = () => {
const counter = this.state.counter + 1;
this.setState({
counter: counter,
hasButtonBeenClicked: counter > 0
});
}
Or, you can use callback parameter of setState.
Related
I'm trying to build an app in React that will render interview questions from HTML, CSS, JS and React. I've set up a data.js file that is an array with objects inside that hold the Id, language, question and answer.
I've set up buttons for PreviousQuestion, NextQuestion and RandomQuestion.
I'm having trouble writing code for the PreviousQuestion, NextQuestion button. I've tried the following code on both but it doesn't work properly:
const questions = data.map((item) => item.question);
class App extends React.Component {
state = {
question: "",
count: 0,
};
prevQuestion = () => {
this.setState({
count: this.state.count - 1,
question: questions[this.state.count]
});
};
nextQuestion = () => {
this.setState({
count: this.state.count + 1,
question: questions[this.state.count]
});
I press nextQuestion this.state.count is 0 then 1 then 2 and then I press
PrevQuestion and this.state.count goes to 3 and only then back to 2 1 ...
Can anyone point me in the right direction on how to solve this so it always increments and decrements properly please?
The issue is that inside setState, your state has not changed yet. So in questions[this.state.count], this.state.count still reflects the old value, not the new one.
Therefore, this should fix your issue with prevQuestion:
prevQuestion = () => {
this.setState({
count: this.state.count - 1,
question: questions[this.state.count - 1]
});
};
And similarly for nextQuestion.
However, as Emile Bergeron stated in the comments, it would be better to only keep track of the count (i.e. the question index) in the state, and not the question itself. You can always access the relevant question using questions[this.state.count], saving you some headaches.
Use prevState in setState like the following one.
prevQuestion = () => {
this.setState(prevState => ({
count: prevState.count -1
question: questions[prevState.count -1]
}));
}
What's happening is that setState does not update the component state immediately, instead essentially queueing a state transition. See this answer. The answers / comments there also mention that you can use some component lifecycle methods such as componentDidUpdate to do something once a state transition has completed.
As others mentioned, you could update the question index by also adding / subtracting 1 from the state.count. Or, since I also don't think it's necessary to track the current question in the component's state, you could just refer to the question object in the render method:
const questions = data.map(item => item.question);
class Component extends Component {
constructor () {
super();
this.state = {
count: 0
}
}
nextQuestion () {
this.setState({ count: this.state.count + 1 });
}
previousQuestion () {
this.setState({ count: this.state.count - 1 });
}
render () {
// Get the question object based on the count in the component state
const question = questions[this.state.count];
return (
<!-- Stuff here -->
<p>{question}</p>
);
}
}
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 }));