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.
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.
1 Answer
Reset to default 26You 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.
redux
. – x-yuri Commented Jul 22, 2017 at 0:07