Is it OK to call setState from within shouldComponentUpdate?
Asked Answered
M

2

14

In response to a state change, I want to trigger another state change. Is that inherently a bad idea?

The specific sort of scenario is that the component is modeled as a state machine that renders different information according to the value of this.state.current_state. But external events can prompt it to experience a state transition, via changes to it's state through a flux store. Here's a contrived scenario to get the idea across:

I think the correct lifecycle method to do this would be shouldComponentUpdate. Something to this effect:

shouldComponentUpdate: function(nextProps, nextState) {
    if (nextState.counter > 4 && this.state.current_state !== DISPLAY_MANY) {
        this.setState({ current_state: DISPLAY_MANY });
    }
    return true;
}

In some child component, counter may get incremented, so rather than inferring what it would display based on the value of some counter variable, I'd like to encode the states explicitly.

The real scenario is more complicated than this, but hopefully this scenario is detailed enough to get the idea across. Is it OK to do what I'm thinking?

EDIT: fixed code example to avoid triggering infinite loop by adding extra state condition

Musetta answered 22/10, 2015 at 20:31 Comment(2)
Why not move the whole FSA logic out of the component?Contradict
please lucybain.com/blog/2017/react-js-when-to-rerenderBoneblack
S
17

shouldComponentUpdate is intended specifically to determine if the component should update at all. To do things like:

if (nextState.counter == this.state.counter && nextProps.foo == this.Props.foo) {
  return false;
}

componentWillReceiveProps is for responding to external (props) changes. There is no equivalent componentWillReceiveState, as pointed out in the docs. Your component (and only your component) triggers it own state changes, usually through one or more of the following events:

  • initial rendering in getInitialState
  • updated props in componentWillReceiveProps
  • user interaction in <input> fields etc, e.g. in custom onChangeInput() functions in your component.
  • in flux: responding to store changes from listeners, typically in custom functions calling getStateFromStores(), where state is updated.

I guess it doesn't make sense to have one function inside a component to create a state change, and then another function inside the same component to intervene before state is updated..

In your case, you could move the logic (to determine if state needs to be updated) to a getStateFromStores() function where you handle store updates.
Or, you could simply leave it in state, and change your render function, so that it renders differently if counter > 4.

Sherly answered 25/10, 2015 at 22:12 Comment(2)
more importantly, setState simply won't work in shouldComponentUpdate. It won't be applied in that rendering, no matter if you return true or false. You would have to do it in the next tick with a timer. Then shouldComponentUpdate will immediately be re-run. The net result is convoluted code. I've done such things, and ended up with unreadable code and found other ways to do what I needed (usually regarding animations).Inadvertency
I highly recommend mastering the low level ReactTransitionGroup. Through that you can accomplish lots of animation stuff clearly and idiomatically. Basically, its willLeave method makes everything easy again. Usually what you're trying to do is keep a component in the DOM while you animate it out--thats where it shines. More importantly though, it lets you do so through a simple interface of comparisons between boolean props to determine whether to hide/show an element. Then your animations are handled separately and cleanly when told to enter/appear or leave.Inadvertency
F
10

No. Please use componentWillReceiveProps instead. It has the same signature of shouldComponentUpdate and you're safe to call this.setState there.

Flyman answered 23/10, 2015 at 0:40 Comment(5)
componentWillReceiveProps only gets you new props, not new state; see the lifecycle documentation here: facebook.github.io/react/docs/component-specs.htmlMusetta
My bad. As the docs said, there's no such thing called componentWillReceiveState. So my suggestion is. extract the logic to another Component and handle that in its componentWillReceiveProps. That makes more sense.Flyman
That would work. So definitively, what I'm suggesting is an antipattern? What's a concrete reason why, and has anyone mentioned so explicitly?Musetta
I think it's just that the docs doesn't expect (or encourage) you to do that (although that might work just fine). More specifically, shouldComponentUpdate should be a pure function based on this.props/state and nextProps/State.Flyman
componentWillReceiveProps is marked for deprecation anywayCelebrity

© 2022 - 2024 — McMap. All rights reserved.