最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - State updates might be asynchronous - Stack Overflow

programmeradmin2浏览0评论

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.

Share Improve this question edited Jul 22, 2017 at 0:49 x-yuri asked Jul 21, 2017 at 23:43 x-yurix-yuri 18.9k15 gold badges129 silver badges180 bronze badges 2
  • Just double checking are you using any state management frameworks like redux or flux? Answer could be different depending – aug Commented Jul 21, 2017 at 23:50
  • I'm going to. But for now I want to make it work without redux. – x-yuri Commented Jul 22, 2017 at 0:07
Add a comment  | 

1 Answer 1

Reset to default 26

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.

发布评论

评论列表(0)

  1. 暂无评论