A few months ago, I started to stop using React setState. It’s not that I don’t need component state anymore, and I don’t use React to manage my component state anymore.

SetState is not very friendly to beginners, and even experienced React programmers are prone to bugs when using setState, such as:

The Bug is caused by forgetting that the React state is asynchronous; This can be seen from the log printing delay.

The React documentation summarizes all the possible problems with setState:

Note:

Never modify this.state directly. Instead, call this.setState to replace the value. Treat this.state as immutable data.

SetState () does not immediately change this.state, but waits in a queue for processing, so when you call setState() and call this.state, it may return the old state.

When you call setState(), there is no guarantee that it will be executed synchronously because it may be batched for performance.

SetState () will always trigger render() for rerendering unless shouldComponentUpdata() controls the render logic. If variable data structures are used and the render logic is not controlled in shouldComponentUpdata(), calling setState() will not trigger a re-render render.

In summary, there are three problems with using setState:

1. setStateIs asynchronous

Many developers don’t realize this at first, but when you set a new state and it doesn’t change, this is the weirdest thing about setState, because setState doesn’t look asynchronous when called. For example:

class Select extends React.Component { constructor(props, context) { super(props, context) this.state = { selection: props.values[0] }; } render() { return ( <ul onKeyDown={this.onKeyDown} tabIndex={0}> {this.props.values.map(value => <li className={value === this.state.selection ? 'selected' : ''} key={value} onClick={() => this.onSelect(value)} > {value} </li> )} </ul> ) } onSelect(value) { this.setState({ selection: value }) this.fireOnSelect() } onKeyDown = (e) => { const {values} = this.props const idx = values.indexOf(this.state.selection) if (e.keyCode === 38 && idx > 0) { /* up */ this.setState({ selection: values[idx - 1] }) } else if (e.keyCode === 40 && idx < values.length -1) { /* down */ this.setState({ selection: values[idx + 1] }) } this.fireOnSelect() } fireOnSelect() { if (typeof this.props.onSelect === "function") this.props.onSelect(this.state.selection) /* not what you expected.. */ } } ReactDOM.render( <Select values={["State.", "Should.", "Be.", "Synchronous."]} onSelect={value => console.log(value)} />, document.getElementById("app") )Copy the code

At first glance, there is nothing wrong with the select component, but there is a bug, as evidenced by the git diagram above. The onSelect() method always fires with the previous state.selection value because fireOnSelect is called before setState is complete. I think you should either rename setState to scheduleState or require incoming callbacks.

This bug is easy to fix, but the tricky part is that it’s hard to spot.

2. setStateCause unnecessary rendering

The second problem with setState is that it always triggers re-rendering, which is often unnecessary. You can detect when it will be rerendered by using the printWasted (the performance tool provided by React). There are three rough reasons why re-rendering is sometimes unnecessary:

  • When the newstateAnd the oldstateIt’s the same. Can be achieved byshouldComponentUpdateTo solve, also can use pure render library to solve.
  • Only sometimesstateIt’s the change to render that matters.
  • Third, some of the timestateIt has nothing to do with the view layer, such as listeners, timers, etc. used to manage eventsstate.

3. setStateIt is impossible to manage the state of all components

Following up on the last point above, not all component states need to be stored and updated by setState. Most complex components typically need to manage timer loops, interface requests, events, and so on. Using setState not only causes unnecessary rendering, but also creates an endless loop.

Use MobX to manage component state

The code is as follows:


import {observable} from "mobx"

import {observer} from "mobx-react"

@observer class Select extends React.Component {

  @observable selection = null; /* MobX managed instance state */

  constructor(props, context) {

    super(props, context)

    this.selection = props.values[0]

  }

  render() {

    return (

      <ul onKeyDown={this.onKeyDown} tabIndex={0}>

        {this.props.values.map(value =>

          <li

            className={value === this.selection ? 'selected' : ''}

            key={value}

            onClick={() => this.onSelect(value)}

          >

            {value}

          </li> 

        )}  

      </ul>

    )

  }

  onSelect(value) {

    this.selection = value

    this.fireOnSelect()

  }

  onKeyDown = (e) => {

    const {values} = this.props

    const idx = values.indexOf(this.selection)

    if (e.keyCode === 38 && idx > 0) { /* up */

      this.selection = values[idx - 1]

    } else if (e.keyCode === 40 && idx < values.length -1) { /* down */

      this.selection = values[idx + 1]

    }

    this.fireOnSelect()

  }

  fireOnSelect() {

    if (typeof this.props.onSelect === "function")

      this.props.onSelect(this.selection) /* solved! */

  }

}

ReactDOM.render(

  <Select 

    values={["State.", "Should.", "Be.", "Synchronous."]} 

    onSelect={value => console.log(value)}

  />,

  document.getElementById("app")

)
Copy the code

The effect is as follows:

There were no unexpected bugs with the mechanism of using synchronized component state.

Not only is the above code snippet neat and elegant, MobX solves all the problems with setState:

Changing state is immediately reflected in component state. This makes code logic and code reuse easier. You don’t have to worry about whether the state is up to date.

MobX determines which states are associated with the view layer at runtime, and states that are temporarily irrelevant to the view layer do not cause re-rendering until they are associated again.

So the renderable and non-renderable states are treated uniformly.

In addition, the silly mistake of modifying state objects directly cannot be made again. Also, don’t worry if you can still use shouldComponentUpdate and PureRenderMixin methods, MobX handles it pretty well. And finally, you might be thinking, how do I wait until the setState process is done, you can use the compentDidUpdate lifecycle.

Finally:

I’ve stopped using React to manage component state and used MobX instead. React is now the real view layer :).

Here is the Dome implemented:

  • withsetStateManage state
  • withMobXManage state