Search code examples
javascriptreactjsreactjs-flux

Race Condition in Store between `render` and `componentDidMount`


Is it safe to set up Store listeners in componentDidMount without at the same time also calling setState or forceUpdate (and thus triggering an immediate re-render)?

In React the common examples seem to be to synchronise Store state in getInitialState or pull Store data in the render before listening, and then start listening in componentDidMount. What prevents a Store change being missed in the interval between the render and setting up the listener in componentDidMount?

If a child component takes a synchronous action in its componentDidMount that changes the store wont this change be missed by the parent component?

If the child component emits an asynchronous action in componentDidMount is there any chance this could change the Store in a way that is missed by the parent component?

Are there any gaps in the React component life-cycle where asynchronous events can get in?

Coming from a background in preemptive asynchronous programming the lack of explicit synchronisation guards in javascript, while often unnecessary in co-operative asynchronous programming, really make me uneasy.


Solution

  • After some testing and digging through the source code I've come to the conclusion that asynchronous events cannot be triggered anywhere in the initial component lifecycle (getInitialState -> componentWillMount -> render -> Children -> componentDidMount).

    Synchronous events called from child components which can change the Store will definitely resolve before the parent can call componentDidMount and will lead to the view and data becoming de-synchronised.

    I've come to the conclusion that the only reason not to register Store listeners in componentWillMount is because they shouldn't be run on servers in isomorphic applications. This to my mind isn't sufficient reason because it registers the listeners out of document order (this can have performance implications) and risks missing synchronous changes to the Store during initial render. It's also conceptually the wrong place to do it.

    I'm planning to register my listeners in componentWillMount wrapped with the browser test:

    componentWillMount: function () {
      if (typeof window !== "undefined") {
        // register listener in browser only
      }
    }