What exactly do they mean? If I understand it correctly, I can't use this.state
when calculating new state, unless I pass a function as a first parameter to setState()
:
// Wrong
this.setState({a: f(this.state)});
// Correct
this.setState(prevState => {return {a: f(prevState)}});
But I can use this.state
to decide what to do:
if (this.state.a)
this.setState({b: 2});
What about props?
// Correct or wrong?
this.setState({name: f(this.props)});
And supposedly I can't expect this.state
to change after calling this.setState
:
this.setState({a: 1});
console.log(this.state.a); // not necessarily 1
Then, say I have a list of users. And a select where I can make one user current:
export default class App extends React.Component {
...
setCurrentUserOption(option) {
this.setState({currentUserOption: option});
if (option)
ls('currentUserOption', option);
else
ls.remove('currentUserOption');
}
handleAddUser(user) {
const nUsers = this.state.users.length;
this.setState(prevState => {
return {users: prevState.users.concat(user)};
}, () => {
// here we might expect any number of users
// but if first user was added, deleted and added again
// two callbacks will be called and setCurrentUserOption
// will eventually get passed a correct value
// make first user added current
if ( ! nUsers)
this.setCurrentUserOption(this.userToOption(user));
});
}
handleChangeUser(user) {
this.setState(prevState => {
return {users: prevState.users.map(u => u.id == user.id ? user : u)};
}, () => {
// again, we might expect any state here
// but a sequence of callback will do the right thing
// in the end
// update value if current user was changed
if (_.get(this.state, 'currentUserOption.value') == user.id)
this.setCurrentUserOption(this.userToOption(user));
});
}
handleDeleteUser(id) {
this.setState(prevState => {
return {users: _.reject(prevState.users, {id})};
}, () => {
// same here
// choose first user if current one was deleted
if (_.get(this.state, 'currentUserOption.value') == id)
this.setCurrentUserOption(this.userToOption(this.state.users[0]));
});
}
...
}
Do all callbacks are executed in sequence after batch of changes to state was applied?
On second thought, setCurrentUserOption
is basically like setState
. It enqueues changes to this.state
. Even if callbacks get called in sequence, I can't rely on this.state
being changed by previous callback, can I? So it might be best not to extract setCurrentUserOption
method:
handleAddUser(user) {
const nUsers = this.state.users.length;
this.setState(prevState => {
let state = {users: prevState.users.concat(user)};
if ( ! nUsers) {
state['currentUserOption'] = this.userToOption(user);
this.saveCurrentUserOption(state['currentUserOption']);
}
return state;
});
}
saveCurrentUserOption(option) {
if (option)
ls('currentUserOption', option);
else
ls.remove('currentUserOption');
}
That way I get queuing of changes to currentUserOption
for free.
React sets its state asynchronously because doing otherwise can result in an expensive operation. Making it synchronous might leave the browser unresponsive. Asynchronous setState calls are batched to provide a better user experience and performance.
To update the state of a component, you use the setState method. However it is easy to forget that the setState method is asynchronous, causing tricky to debug issues in your code.
State Updates are MergedWhen you call setState() , React merges the object you provide into the current state . In the example below, we're updating the variable dogNeedsVaccination independently of the other state variables. The merging is shallow, so this.
When you directly update the state, it does not change this. state immediately. Instead, it creates a pending state transition, and accessing it after calling this method will only return the present value. You will lose control of the state across all components.
You didn't really ask a very specific question. "What does this mean" is not very much to go on. But you generally seem to understand the basics.
There are two possible ways to call setState()
: either by passing it an object to get merged into the new state, or by passing it a function which returns an object which gets merged in a fashion similar to the first way.
So you either do this:
// Method #1
this.setState({foo: this.state.foo + 1}, this.someCallback);
Or this:
// Method #2
this.setState((prevState) => {return {foo: prevState.foo + 1}}, this.someCallback);
The main difference is that with method #1, foo
will get incremented by 1 based on whatever state it was at the time you call setState()
, while in method #2, foo
will get incremented by 1 based on whatever the previous state was in the instant that the arrow function runs. So if you have multiple setState()
calls that happen at the "same" time before the actual state update, with method #1 they may conflict and/or be based on outdated state, while with method #2 they are guaranteed to have the most up-to-date state because they update synchronously, one after another, in the state update phase.
Here is an illustrative example:
Method #1 JSBIN Example
// Method #1
class App extends React.Component {
constructor(props) {
super(props);
this.state = {n: 0};
this.increment.bind(this);
}
componentDidMount() {
this.increment();
this.increment();
this.increment();
}
increment() {
this.setState({n: this.state.n + 1}, () => {console.log(this.state.n)});
}
render() {
return (
<h1>{this.state.n}</h1>
);
}
}
React.render(
<App />,
document.getElementById('react_example')
);
In the above: you would see this in the console:
> 1
> 1
> 1
And the final value of this.state.n
would be 1
. All of the setState()
calls were enqueued when the value of n
was 0
, so they're all simply set it to 0 + 1
.
Method #2 JSBIN Example
// Method #2
class App extends React.Component {
constructor(props) {
super(props);
this.state = {n: 0};
this.increment.bind(this);
}
componentDidMount() {
this.increment();
this.increment();
this.increment();
}
increment() {
this.setState((prevState) => {return {n: prevState.n + 1}}, () => {console.log(this.state.n)});
}
render() {
return (
<h1>{this.state.n}</h1>
);
}
}
React.render(
<App />,
document.getElementById('react_example')
);
In the above, you would see this in the console:
> 3
> 3
> 3
And the final value of n
would be 3
. Like with method #1, all of the setState()
calls were enqueued at the same time. However, since they use a function to synchronously update in order using the most current state - including changes to state made by concurrent state updates - they properly increment n
three times as you would expect.
Now, why does console.log()
show 3
three times for method #2, instead of 1
, 2
, 3
? The answer is that setState()
callbacks all happen together at the end of the state update phase in React, not immediately after that particular state update happens. So in that regard methods #1 and #2 are identical.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With