19

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?

akaprog
  • 191
  • 1
  • 1
  • 3
  • 2
    setState is meant to be asynchronous, and is batched by React whenever possible for performance – user120242 May 20 '15 at 01:06
  • 1
    You may also want to send a callback http://stackoverflow.com/questions/25172850/reactjs-this-setstate-out-of-sync-with-this-state-myvar – Adam Grant Aug 03 '15 at 23:07

2 Answers2

45

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

this.setState(prevState => {
  return {
    someProp: prevState.someProp + 1,
  };
});

Even if two different parts of your application do 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 could 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 previous state as an argument, which lets you avoid data races.

Jozef Mikušinec
  • 193
  • 2
  • 10
Anders Ekdahl
  • 22,685
  • 4
  • 70
  • 59
  • 1
    I believe that `update` function should do exactly the same. To clarify - in my case I do access different properties of complex state object overwriting them. – akaprog May 20 '15 at 17:33
  • 5
    You shouldn't change the object that is passed in to that function. You should return a new object with the change state instead. And the parameter is called `prevState` not `currentState` which also hints at that. – Daniel Hilgarth Apr 16 '18 at 18:26
  • 1
    What the hell just happened ? :D That was amazing thank you – Steve Moretz Feb 01 '21 at 15:33
11

According to React documentation, as mentioned in the first answer, you can pass a function to ensure the atomicity's operation. The thing is that you should not modify the state but return the new value inside the passed function:

setState(function(previousState, currentProps) {
  return {myInteger: previousState.myInteger + 1};
});

https://facebook.github.io/react/docs/component-api.html

Borja Martín
  • 111
  • 2
  • 2