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

javascript - react setState callback doesn't have the updated state - Stack Overflow

programmeradmin2浏览0评论

if monthOffset = 12 the condition will evaluate to true and update the yearOffset state to 2017 if yearOffset = 2018. Based on the react docs and other answers I've read, the callback function in this.setState fires after the state has been updated, yet the console.log() is still outputting 2018. I've tried a couple different methods of implementing this code based on answers form other related questions but mine isn't working. I'm not sure why.

handleClick(e) {
  const { monthOffset, yearOffset } = this.state
  this.setState({ monthOffset: monthOffset - 1 })
  if ( monthOffset - 1 === 11 ) { this.setState((prevState) => { 
    return { yearOffset: prevState.yearOffset - 1 } },
    () => {console.log("yearOffset", yearOffset)}
  )}
  console.log("clicked")
}

if monthOffset = 12 the condition will evaluate to true and update the yearOffset state to 2017 if yearOffset = 2018. Based on the react docs and other answers I've read, the callback function in this.setState fires after the state has been updated, yet the console.log() is still outputting 2018. I've tried a couple different methods of implementing this code based on answers form other related questions but mine isn't working. I'm not sure why.

handleClick(e) {
  const { monthOffset, yearOffset } = this.state
  this.setState({ monthOffset: monthOffset - 1 })
  if ( monthOffset - 1 === 11 ) { this.setState((prevState) => { 
    return { yearOffset: prevState.yearOffset - 1 } },
    () => {console.log("yearOffset", yearOffset)}
  )}
  console.log("clicked")
}
Share Improve this question edited Feb 9, 2019 at 18:02 Ryan Sam asked Aug 22, 2018 at 1:01 Ryan SamRyan Sam 2,9974 gold badges22 silver badges35 bronze badges 1
  • Have you tried using console.log("yearOffset", yearOffset) instead of an anonymous function inside the setState callback? – imgnx Commented Aug 22, 2018 at 1:31
Add a comment  | 

4 Answers 4

Reset to default 6

Perhaps you could simplify your logic in the following way, to avoid multiple calls to setState which may be causing unexpected results:

handleClick(e) {

  const { monthOffset, yearOffset } = this.state

  // call setState once
  this.setState({ 

    // Always decrement month offset
    monthOffset : monthOffset - 1, 

    // Only decrement year offset if current month offset === 12
    yearOffset : (monthOffset === 12) ? yearOffset - 1 : yearOffset

  }, () => {

    console.log("state updated to", this.state)
  })

  console.log("clicked")
}

The documentation says that the callback always works, but I know from experience that it doesn't always return what you're expecting. I think it has something to do with using mutable objects inside the state itself.

Docs: https://reactjs.org/docs/react-component.html#setstate

You can't completely rely on the callback. Instead, what you can do is create

var stateObject = this.state

Make any necessary changes to the object:

stateObject.monthOffset -= 1

and then set the state like this:

this.setState(stateObject);

That way you have a copy of the nextState inside stateObject

To clarify: You want to do all of the evaluation before you set the state, so do:

monthOffset -= 1

then if (monthOffset === 12) yearOffset -=1;

then var stateObj = {monthOffset: monthOffset, yearOffset: yearOffset}

then this.setState(stateObj);


From the documentation: "The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead."

So basically if you want to get the next state, you should either have a copy of it inside your function that calls setState() or you should get the nextState from componentDidUpdate


Afterthoughts: Anything that you pass into setState() as a parameter is passed by reference (not by value). So, if you have an object SearchFilters: {} within your state, and inside your call to setState(), you have

setState({SearchFilters: DEFAULT_SEARCH_FILTERS}); // do not do this

You may have set SearchFilters to DEFAULT_SEARCH_FILTERS in an effort to clear out a form called "Search Filters", but instead you will have effectively set DEFAULT_SEARCH_FILTERS (a constant) to SearchFilters, clearing out your DEFAULT_SEARCH_FILTERS.

Expected behavior? You tell me.

There are two prevalent patterns to calling setState in React: object setState, and "functional setState". Functional setState is generally used when the current state (or "previous state", or whatever you want to call the old state) is invoked in the setState call. This is done because setState is asynchronous, and as a result subsequent setStates can sometimes run before React has managed to complete the first setState cycle.

You have used existing state in your setState call, so it would be an appropriate place to use functional setState. Replace

this.setState({ monthOffset: monthOffset - 1 })

with

this.setState(monthOffset => {return {monthOffset: monthOffset - 1}})

If you are like me when I first saw this, you might be thinking, "Huh? How is that any different than what I have?" The difference is that when setState is passed a function instead of an object, it queues the update instead of going through its usual resolution process, which ensures things get done in order.

Or you might not be thinking this; you have actually used functional setState in your second setState call. Using it in your first one too will make sure things are queued correctly.

This happens because you are using the yearOffset that you've read on top, from the previous state (before your this.setState has run).
You should instead read the state from the callback directly, like this:

() => console.log("yearOffset", this.state.yearOffset)
发布评论

评论列表(0)

  1. 暂无评论