ReactJS concurrent SetState race condition

akaprog picture akaprog · May 20, 2015 · Viewed 9k times · Source

I have a component structure like this

<A>
    <B>
        <C/>
        <C/>
    </B>
    <D>
       <E/>
       <E/>
    </D>
</A>

Idea is that actions on components in block E are processed by a function of component A to state of A and than passed down to B and C as props. I know, that better way was to use Flux, pubsub-js or other Store-message system, but hope if someone can explain why correct to the best of my understanding solution doesn't work.

Calling this function of component A simalteneously from multiple instances of component E leads to race condition with only one change in state (instead of each function call providing a change)

updateState(value,index){
   this.setState(update(this.state, {
        a: {
          [index]: {
            b: {
             $set: value
            }
          }
        }
    })
);
}

Function update here comes from

import update from 'react/lib/update';

Bad solution that goes against ReactJS reccomended practices, but works well:

updateState(value,index){
   this.state.a[index].b=value;
   this.forceUpdate();
);
}

My question is:

Is it a bug, that multiple simalteneous setState invokes a race condition, or I'm doing something wrong without understnding it?

Answer

Anders Ekdahl picture Anders Ekdahl · May 20, 2015

You probably want to pass a function to setState which should remove such race conditions. Something like:

this.setState(currentState => {
  currentState.someProp++;
  return currentState;
});

Even if two different parts of your application does this at the same time, both functions will still be called and someProp will be incremented twice.

If you were to do this instead: this.setState({someProp: this.state.someProp + 1}) it would only be incremented once because this.state.someProp isn't updated directly after calling setState. But when passing a function to setState you get the current pending state as an argument, which gives you the option to either just increment like in my example, or merge your mutation with any other mutation that took place.