Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Race Condition in React setState

The Problem:

Multiple children of a component are having events triggered near simultaneously. Each of these events are handled by handleChange style functions which use React's immutability helpers to merge complex objects into the state of the controlling component, via something similar to;

this.setState(React.addons.update(this.state, {$merge: new_value_object}));

This works fine when the events trigger independently, but when multiple events cause updates to the state in this way, each is individually merging from the old version of the state. I.e. (psuedo-code, not intended to execute).

function logState() { console.log(this.state) }

logState(); // {foo: '', bar: ''}

var next_value_object_A = {foo: '??'}
var next_value_object_B = {bar: '!!'}

this.setState(React.addons.update(this.state, {$merge: new_value_object_A}),
    logState); 
this.setState(React.addons.update(this.state, {$merge: new_value_object_B}),
    logState);

Would produce;

{foo: '??', bar: ''}
{foo: '', bar: '!!'}

Terrible solution that I don't want to use:

The following seems to work, but also seems to be a major anti-pattern;

setSynchronousState: function(nextState){
    this.state = React.addons.update(this.state, {$merge: nextState});
    this.setState(this.state);
}

This relies on modifying the State directly. I don't see any immediate problems in running this code, and it does solve the problem at hand, but I have to imagine that I'm inheriting some massive technical debt with this solution.

A slightly better version of this solution is;

getInitialState: function(){
    this._synchronous_state = //Something
    return this._synchronous_state;
},

_synchronous_state: {},

setSynchronousState: function(nextState){
   this._synchronous_state = React.addons.update(this._synchronous_state, {$merge: nextState});
   this.setState(this._synchronous_state);
}

Which successfully avoids touching this.state directly, though now we have the issue of conflicting information being passed around the application. Each other function now needs to be congnizant of whether it is accessing this.state or this._synchronous_state.

The Question:

Is there a better way to solve this problem?

like image 548
Jake Haller-Roby Avatar asked Jun 28 '16 01:06

Jake Haller-Roby


1 Answers

Answering my own question in case anyone else ever sees this;

this.setState can take in a function as it's argument, rather than an object, which looks like this;

this.setState(function(state){ 
    ...
    return newState 
});

Which allows you to access the current state (at time of execution) via the passed argument to that function.

like image 94
Jake Haller-Roby Avatar answered Oct 06 '22 09:10

Jake Haller-Roby