Why need to pass a callback accepting the previous state? - reactjs

class ClassCounterTwo extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
incrementCount = () => {
this.setState(prevState => {
return {
count: prevState.count + 1
}
})
}
render() {
return (
<div>
<button onClick={this.incrementCount}>Count {this.state.count}</button>
</div>
)
}
When updating state based on previous state, why does React make mistakes often if you don't pass a callback accepting the previous state like this:
this.setState(prevState => {
return {
count: prevState.count + 1
}
Sorry, just asking the question because I just saw somewhere the founder of Node.js said, "Try to push yourself to understand the system."

why does React make mistakes often if you don't pass a callback accepting the previous state
When you set state, the component will rerender very soon, but not synchronously. This lets react batch up multiple changes and apply them all at once, so that only one render is needed when state is set multiple times in the same call stack.
So if you wrote two lines of code back to back like this:
this.setState({ count: 1 });
this.setState({ count: 2 });
It will only render once, with the 2 as the new state.
The problem comes if you write code that checks this.state with the assumption that this.state is up to date. For example:
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
If count was at 0 when this code began, then i will be telling react to set the count to 1, and then telling react to set it to 1 again, since this.state.count didn't change in between these two lines. React will only render once, but since i told it 1 both times, that's what we'll be left with on the screen.
In most cases, these lines won't appear back to back in your code; they may be executing on different event listeners in different parts of your component, but in any event, the core point is that this.state.count only tells you what the count was when your code is executing, not what it is when we're just about to render.
So if you need to base your new state on the old one, there's the function version of setState. React will call the function and be sure to pass in the most recent value, letting you calculate the next state correctly no matter how many times you're updating it.

Related

Will mutating React's state directly before calling setState cause any problem?

First of all, I know there's a similar question but its accepted answer does not actually answer the question, so I am asking it again.
It is well known that we should not mutate the state directly, because of many issues that can happen. I personally always make a copy of the data I will update, mutate this copy, and assign it to the original variable in the state with a call to setState, which is the common and canonical way of updating the state:
let copy = JSON.parse(JSON.stringify(this.state.someDeepObject))
copy.a.b.c = 5
this.setState({ someDeepObject: copy })
However a colleague of mine uses an alternative way of updating the state. He mutates the state directly, and then uses the updated value in the state to update the state with setState, enabling a re-render and preventing all problem that a direct mutation would normally cause:
this.state.someDeepObject.a.b.c = 5
this.setState({ someDeepObject: this.state.someDeepObject })
Since he calls setState right after mutating the state, this way of mutating the state, although tricky and not quite reliable, works, and has never bugged anywhere it is used in the application, as far as I know.
It is not the canonical way of mutating the state, however, it works. I would use the canonical way instead, but my colleague is a pragmatic man and, if it works and is reliable (which his code seems to be), he won't change it.
A more complete example of the situation will show you that his code does work:
class Button extends React.Component {
constructor() {
super();
this.state = {
count: 0,
};
}
updateCount() {
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
});
}
updateCountDirectly() {
this.state.count = this.state.count + 1
}
updateCountDirectlyButSafely() {
this.state.count = this.state.count + 1
this.setState({ count: this.state.count })
}
render() {
return (
<div>
<button onClick={() => this.updateCount()} >
Clicked {this.state.count} times (normal)
</button>
<button onClick={() => this.updateCountDirectly()} >
Clicked {this.state.count} times (directly)
</button>
<button onClick={() => this.updateCountDirectlyButSafely()} >
Clicked {this.state.count} times (directly but safely)
</button>
</div>
);
}
}
React.render(<Button />, document.getElementById('app'));
(copy paste this in CodePen to test it)
Will his code cause any problem?
I won't use it myself but, if it doesn't actually cause any problem, then I won't bother him with it anymore.
Probably not... But it is not guaranteed. React could be doing internal stuff before it actually updates the state that is rendered and this could interfere.
Also, it seems to be working in your example only randomly. Because if you mutate the state without going through setState React has no way to tell that it should re-render the component. In your example the "setDirectly" method just seems to work because React re-renders the component anyhow (maybe because it does so after onClick callbacks).

When to use setState callback argument versus directly passing the value

According to React docs, I found there are two forms of setState:
Form 1: setState({someState: someValue})
Form 2: setState((preState) => ({someState: doSomething(preState)}))
Form 1 sets state directly, form 2 sets state by using a callback function.
In the Using state correctly section, I've been told that form 1 may not be safe. Does this mean I should always to use form 2 to set state correctly? I soon noticed there is another example here which uses form 1 to update state. Is it an incorrect example?
The form 1 and form 2 may both right, but in which situations is it perfectly safe to use form 1, and in which situations should I use form 2?
I wrote a note on setState. I hope it will help you to use it properly. If you read it sincerely and understand it, you're gonna be better at using it to manage state.
setState
It's asynchronous
state = { count: 0};
increment() {
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
console.log(this.state.count) // 0
}
increment()
console.log(this.state.count); // 1
And, the final value of this.state.count will be 1 after completion of the calling incemenent()
Because React batch all calls up, and figure out the result and then efficiently make that change. Kind of this pure JavaScript code, merging where the last one wins
newState = Object.assign(
{},
firstSetStateCall,
secondSetStateCall,
thirdSetStateCall,
);
So, we can say here everything has to do with JavaScript object merging. So there's another cool way, where we pass a function in setState instead of object.
state = { count: 0};
increment() {
this.setState( (state) => { return { count: state.count + 1} } );
this.setState( (state) => { return { count: state.count + 1} } );
this.setState( (state) => { return { count: state.count + 1} } );
}
increment();
console.log(this.state.count) // 3
This time we will get 3 because earlier it was possible to merge objects but it's not possible to merge functions so it works like synchronous.
But another nice application of this method of passing parameters in this.setState is you can implement logic before returning the objects from the function
this.setState( (state) => { if(state.count === 0) return { count: state.count + 1} } );
Not only that, the function we pass inside setState takes another parameter, props.
this.setState((state, props) => { //play here })
But, the function we're passing it could grow messy by time, so what? Just make a regular JavaScript function
and pass it to the setState
this.setState(fn)
If SetState is asynchronous how we can do an operation just after the state gets updated?
setState actually takes two arguments, second one of these two is callback function, that is invoked after state is updated,
this.setState (
(state, props) => {
// code here
},
() => {console.log("updated state", this.state)}
)
The choice of form depends on whether the next state uses the value of the current state.
If the new state relies on the current state, use form 2. Otherwise use form 1.

does the state's changes with multiple this.setState calls are aggregated? [duplicate]

I know that React may perform state updates asynchronously and in batch for performance optimization. Therefore you can never trust the state to be updated after having called setState. But can you trust React to update the state in the same order as setState is called for
the same component?
different components?
Consider clicking the button in the following examples:
1. Is there ever a possibility that a is false and b is true for:
class Container extends React.Component {
constructor(props) {
super(props);
this.state = { a: false, b: false };
}
render() {
return <Button onClick={this.handleClick}/>
}
handleClick = () => {
this.setState({ a: true });
this.setState({ b: true });
}
}
2. Is there ever a possibility that a is false and b is true for:
class SuperContainer extends React.Component {
constructor(props) {
super(props);
this.state = { a: false };
}
render() {
return <Container setParentState={this.setState.bind(this)}/>
}
}
class Container extends React.Component {
constructor(props) {
super(props);
this.state = { b: false };
}
render() {
return <Button onClick={this.handleClick}/>
}
handleClick = () => {
this.props.setParentState({ a: true });
this.setState({ b: true });
}
}
Keep in mind that these are extreme simplifications of my use case. I realize that I can do this differently, e.g. updating both state params at the same time in example 1, as well as performing the second state update in a callback to the first state update in example 2. However, this is not my question, and I am only interested in if there is a well defined way that React performs these state updates, nothing else.
Any answer backed up by documentation is greatly appreciated.
I work on React.
TLDR:
But can you trust React to update the state in the same order as setState is called for
the same component?
Yes.
different components?
Yes.
The order of updates is always respected. Whether you see an intermediate state "between" them or not depends on whether you're inside in a batch or not.
In React 17 and earlier, only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.
Starting from React 18, React batches all updates by default. Note that React will never batch updates from two different intentional events (like clicks or typing) so, for example, two different button clicks will never get batched. In the rare cases that batching is not desirable, you can use flushSync.
The key to understanding this is that no matter how many setState() calls in how many components you do inside a React event handler, they will produce only a single re-render at the end of the event. This is crucial for good performance in large applications because if Child and Parent each call setState() when handling a click event, you don't want to re-render the Child twice.
In both of your examples, setState() calls happen inside a React event handler. Therefore they are always flushed together at the end of the event (and you don't see the intermediate state).
The updates are always shallowly merged in the order they occur. So if the first update is {a: 10}, the second is {b: 20}, and the third is {a: 30}, the rendered state will be {a: 30, b: 20}. The more recent update to the same state key (e.g. like a in my example) always "wins".
The this.state object is updated when we re-render the UI at the end of the batch. So if you need to update state based on a previous state (such as incrementing a counter), you should use the functional setState(fn) version that gives you the previous state, instead of reading from this.state. If you're curious about the reasoning for this, I explained it in depth in this comment.
In your example, we wouldn't see the "intermediate state" because we are inside a React event handler where batching is enabled (because React "knows" when we're exiting that event).
However, both in React 17 and earlier versions, there was no batching by default outside of React event handlers. So if in your example we had an AJAX response handler instead of handleClick, each setState() would be processed immediately as it happens. In this case, yes, you would see an intermediate state in React 17 and earlier:
promise.then(() => {
// We're not in an event handler, so these are flushed separately.
this.setState({a: true}); // Re-renders with {a: true, b: false }
this.setState({b: true}); // Re-renders with {a: true, b: true }
this.props.setParentState(); // Re-renders the parent
});
We realize it's inconvenient that the behavior is different depending on whether you're in an event handler or not. In React 18, this is no longer necessary, but before that, there was an API you can use to force batching:
promise.then(() => {
// Forces batching
ReactDOM.unstable_batchedUpdates(() => {
this.setState({a: true}); // Doesn't re-render yet
this.setState({b: true}); // Doesn't re-render yet
this.props.setParentState(); // Doesn't re-render yet
});
// When we exit unstable_batchedUpdates, re-renders once
});
Internally React event handlers are all being wrapped in unstable_batchedUpdates which is why they're batched by default. Note that wrapping an update in unstable_batchedUpdates twice has no effect. The updates are flushed when we exit the outermost unstable_batchedUpdates call.
That API is "unstable" in the sense that we will eventually remove it in some major version after 18 (either 19 or further). You safely rely on it until React 18 if you need to force batching in some cases outside of React event handlers. With React 18, you can remove it because it doesn't have any effect anymore.
To sum up, this is a confusing topic because React used to only batch inside event handlers by default. But the solution is not to batch less, it's to batch more by default. That's what we're doing in React 18.
This is actually a quite interesting question but the answer shouldn't be too complicated. There is this great article on medium that has an answer.
1) If you do this
this.setState({ a: true });
this.setState({ b: true });
I don't think that there will be a situation where a will be true and b will be false because of batching.
However, if b is dependent on a then there indeed might be a situation where you wouldn't get the expected state.
// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
After all the above calls are processed this.state.value will be 1, not 3 like you would expect.
This is mentioned in the article: setState accepts a function as its parameter
// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
This will give us this.state.value === 3
Multiple calls during the same cycle may be batched together. For example, if you attempt to increment an item quantity more than once in the same cycle, that will result in the equivalent of:
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
https://reactjs.org/docs/react-component.html
as in doc
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.
it will preform the change as in queue (FIFO : First In First Out) the first call will be first to preform
In this case, it does not. I have three boxes and those state and handleClick
const [openedIndex, setOpenedIndex] = useState(-1);
const handleClic = (index) => {
console.log("opened index to test delayed state update", openedIndex);
if (index === openedIndex) {
setOpenedIndex(-1);
} else {
setOpenedIndex(index);
}
};
When I click to open and close it works:
if I get a reference to element and run $0.click() when it is opened, it closes
now what happens, if run $0.click() twice in a row, it should open and then closes but it does not
Similar post here: Updating data on changedropdown in Reactjs gets delayed
onClick works but onDoubleClick is ignored on React component

Next and previous buttons not working properly

I am building a react app which renders differnet components on pressing the next and back buttons. The problem i am facing is that the both these buttons are reacting to the previous button press. Like if a first press next then nothing will happen. If then i press back then it is responding to the previous button press of next.
class Card extends Component {
constructor()
{
super();
this.state = {
button:"",
i:0
}
}
onClick = (event)=>{
this.setState({button: event.target.id})
if(this.state.button==="1")
{
this.setState({ i: this.state.i + 1 });
}
else if(this.state.button==="2")
{
this.setState({ i: this.state.i - 1 });
}
console.log(this.state.i)
}
render() {
return(
<div className="App">
<div >
<NavBar onButtonClick={this.onClick}/>
<CardList i={this.state.i} />
</div>
</div>
);
}
}
export default Card;
setState in React is batched by React, essentially it means, if you do 3 set state calls one after the other, they are all called sequentially.
I recommend reading the official docs for more details.
Now, on to your problem: you are setting a state based on the "previous value" in that state:
this.setState({ i: this.state.i - 1 });
This will cause "unexpected" behaviour due to the batched mode, as the this.state.i could've changed at the time it is actually called.
Here is what React docs recommend to do:
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))
There is a second signature for setState method, which accepts a function with the signature: (state, props) => stateChange where props is optional, if you don't want to use it, no need to pass it.
The common mistake you seem to be doing is reading this.state after calling this.setState, this will not work.
The code below is not perfect, but hopefully shows, what you need to correct.
onClick = (event)=>{
var buttonId = event.target.id;
if(buttonId==="1")
{
this.setState((state) => {
return { i: state.i + 1, button: 1 }
});
}
else if(buttonId==="2")
{
this.setState((state) => {
return { i: state.i - 1, button: 2 }
});
}
}
Additional reading: If you still need to understand how to use functions in setState, I can recommend this article: https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b
this.setState takes a little bit of time to update the entire state, so you cannot reliably execute this.setState({button: event.target.id}) and then immediately afterward check the this.state.button - it's likely that the state hasn't finished being set yet.
Instead, I'd recommend setting your conditional based on the event.target.id, like this:
if (event.target.id === "1") {
this.setState({ i: this.state.i + 1 });
} else if (event.target.id === "2") {
this.setState({ i: this.state.i - 1 });
}
I would wait until the top of your render method to console.log your state for debugging purposes, as it will have completed being updated by then.
For more information on how to use setState properly, you can see this article, especially the section entitled "State Updates May Be Asynchronous".

ReactJS difference between setting state directly and through method argument

I have started learning ReactJS and realize that there are two way I can change the state. Both works fine without any error or warning.
What is the main difference? is there a reason or need I should use second version?
consider I want to modify following state on each click
this.state = { count: 0 }
First Version
handleClick = () => {
this.setState({count: this.state.count+1})
}
Second Version
handleClick = () => {
this.setState(prvState => {
return {
count: prvState+1
}
})
}
If you are setting the next state value based on the previous state
value then passing in a function (second version) as the first
parameter of this.setState instead of an object is the recommended
solution.
handleClick = () => {
this.setState(prvState => {
return {
count: prvState+1
}
})
}
The reason is that this.state may be updated asynchronously and that's why you should not rely on their values for calculating the next state.
In the first version, there's a high possibility that the value of count is incorrect especially once your application gets bigger.
If you rely on the previous state (like in your case you need the previous counter value), the second approach is preferred. It guarantees that prvState holds the current state. This is important if you have two setState modifying the state in the same render loop (remember that React may batch multiple setState calls together), e.g.
this.setState({count: this.state.count+1})
this.setState({count: this.state.count+1})
has the problem that the count gets only incremented once. While
this.setState((prevState) => {count: prevState.count+1})
this.setState((prevState) => {count: prevState.count+1})
guarantees that it gets incremented twice as intended (independent of the order the setState are handled by React)

Resources