Proper handling of multiple sequential state updates - javascript

This example is a bit contrived, but bear with me. Say I have two functions as members in a React component:
var OrderApp = React.createClass({
getInitialState: function() {
return {
selectedOrders: {},
selectedLineItems: {}
};
},
setOrderSelected: function(order, newSelectedState) {
var mutation = {};
mutation[order.id] = {$set: newSelectedState};
var newSelectedOrders = React.addons(this.state.selectedOrders, mutation);
_.each(order.lineItems, function(lineItem) {
setLineItemSelected(lineItem, newSelectedState);
});
this.setState({selectedOrders: newSelectedOrders});
},
setLineItemSelected: function(lineItem, newSelectedState) {
var mutation = {};
mutation[lineItem.id] = {$set: newSelectedState};
var newSelectedLineItems = React.addons(this.state.selectedLineItems, mutation);
this.setState({seelctedLineItems: newSelectedLineItems});
}
});
At the completion of calling OrderApp.setOrderSelected(order, true), only the last lineItem selected state appears to have changed in my component's state. I'm guessing this is because React is batching the calls to setState, and so when I'm calling:
var newSelectedLineItems = React.addons(this.state.selectedLineItems, mutation);
in the second function, I'm picking up the original state each time, and so only the last mutation takes effect.
Should I be collecting all of the state mutations in the parent function and only calling setState once? This feels a bit clunky to me, and I'm wondering if I'm missing a simpler solution.

You should never be calling this.setState more than once in a given thread of execution within a component.
So you have a couple of options:
Use forceUpdate instead of setState
You don't HAVE to use this.setState - you can modify the state directly and use this.forceUpdate after you're done.
The main thing to remember here is that you should treat this.state as tainted as soon as you start modifying it directly. IE: don't use the state, or properties you know you've modified until after you've called this.forceUpdate. This isn't usually a problem.
I absolutely prefer this method when, you're dealing with nested objects within this.state. Personally I'm not a huge fan of React.addons.update because I find the syntax extremely clunky. I prefer to modify directly and forceUpdate.
Create an object that you pass around until finally using it in setState
Collect an object of all your changes, to pass to this.setState at the end of your thread of execution.

Unfortunately, this is a problem with shallow merge. You do have to collect the updates, and apply them at one time.
feels a bit clunky to me
This is a really good sign that you should be using a mixin! This code looks pretty terrible, but you can just pretend it looks pretty when you're using it.
var React = require('react/addons');
var UpdatesMixin = {
componentDidMount: function(){
this.__updates_list = [];
},
addUpdate: function(update){
this.__updates_list.push(update);
if (this.__updates_list.length === 1) {
process.nextTick(function(){
if (!this.isMounted()) {
this.__updates_list = null;
return;
}
var state = this.__updates_list.reduce(function(state, update){
return React.addons.update(state, update);
}, this.state);
this.setState(state);
this.__updates_list = [];
}.bind(this));
}
}
};
requirebin
It buffers all updates that occur synchronously, applies them to this.state, and then does a setState.
The usage looks like this (see the requirebin for a full demo).
this.addUpdate({a: {b: {$set: 'foo'}};
this.addUpdate({a: {c: {$set: 'bar'}};
There are some places it could be optimized (by merging the updates objects, for example), but you can do that later without changing your components.

Related

React - Changing the state without using setState: Must avoid it?

My code works, but I have a best practice question: I have an array of objects in the state, and a user interaction will change a value of one object at a time. As far as I know, I'm not supposed to change the state directly, i should always use setState instead. If I want to avoid that with any price, I will deep clone the array by iteration, and change the clone. Then set the state to the clone. In my opinion avoiding to change the state that I will change later anyway is just decreasing my performance.
Detailed version:
this.state.data is an array of objects. It represents a list of topics in a forum, and a Favorite button will toggle, calling clickCollect().
Since I have an array in the state, when I change the is_collected property of one item, I need to create a copy of the array to work with, and after changing to the new value, I can set it to the state.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
var data = this.state.data : This would copy the pointer to the array and push(), shift(), etc would alter the state directly. Both data and this.state.data will be affected.
var data = this.state.data.slice(0) : This makes a shallow clone, push and shift doesn't change the state but in my clone I still have pointers to the elements of the state's array. So if I change data[0].is_collected, this.state.data[0].is_collected gets changed as well. This happens before I call setState().
Normally I should do:
var data = [];
for (var i in this.state.data) {
data.push(this.state.data[i]);
}
Then I change the value at index, setting it to true when it's false or false when it's true:
data[index].is_collected = !data[index].is_collected;
And change state:
this.setState({data: data});
Consider my array is relatively big or enormously big, I guess this iteration will reduce the performance of my APP. I would pay that cost if I knew that it is the right way for any reason. However, in this function (clickCollect) I always set the new value to the state, I'm not waiting for a false API response that would say to stop making the change. In all cases, the new value will get into the state. Practically I call setState only for the UI to render again. So the questions are:
Do I have to create the deep clone in this case? (for var i in ...)
If not, does it make sense to make a shallow clone (.slice(0)) if my array contains objects? The changes are being made on the objects inside of the array, so the shallow clone still changes my state, just like a copy (data = this.state.data) would do.
My code is simplified and API calls are cut out for simplicity.
This is a beginner's question, so a totally different approach is also welcome. Or links to other Q & A.
import React from 'react';
var ForumList = React.createClass({
render: function() {
return <div className="section-inner">
{this.state.data.map(this.eachBox)}
</div>
},
eachBox: function(box, i) {
return <div key={i} className="box-door">
<div className={"favorite " + (box.is_collected ? "on" : "off")} onTouchStart={this.clickCollect.bind(null, i)}>
{box.id}
</div>
</div>
},
getInitialState: function() {
return {data: [
{
id: 47,
is_collected: false
},
{
id: 23,
is_collected: false
},
{
id: 5,
is_collected: true
}
]};
},
clickCollect: function(index) {
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
}
});
module.exports = ForumList;
Personally I don't always follow the rule, if you really understand what you are trying to do then I don't think it's a problem.
var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});
In this case, mutating state and calling the setState again like this is fine
this.state.data[index].is_collected = !this.state.data[index].is_collected;
this.setState({data: this.state.data});
The reason you should avoid mutating your state is that if you have a reference to this.state.data, and calling setState multiple times, you may lose your data:
const myData = this.state.data
myData[0] = 'foo'
this.setState({ data: myData })
// do something...
// ...
const someNewData = someFunc()
this.setState({ data: someNewData })
myData[1] = 'bar' // myData is still referencing to the old state
this.setState({ data: myData }) // you lose everything of `someNewData`
If you really concerned about this, just go for immutable.js
Muting the state directly breaks the primary principle of React's data flow (which is made to be unidirectional), making your app very fragile and basically ignoring the whole component lifecycle.
So, while nothing really stops you from mutating the component state without setState({}), you would have to avoid that at all costs if you want to really take advantage of React, otherwise you would be leapfrogging one of the library's core functionalities.
If you want follow react best practices, you should do shallow copy of all your array, when you change any property. Please look into "immutable" library implementation.
But, from my experience, and from my opinion, setState method should be called if you have "shouldCompomenentUpdate" implementations. If you think, that your shallow copy will be consume much more resources, then react virtual dom checks, you can do this:
this.state.data[0].property = !this.state.data[0].property;
this.forceUpdate();
If I understood your question right, you have an array of objects and when a property of a single object in array changes,
Create a deep clone of the array and pass to setState
Create a shallow clone and pass to setState
I just checked with the redux sample todo app and in case of a single property of an object changes you've to create a fresh copy of that single object not the entire array. I recommend you to read about redux and if possible use to manage the state of your app.

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?
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.

in React, are there any problems using Set() in a component state?

are there limitations on what sort of objects that can be kept in a component's state? would be very nice to use Set(), but don't know if that's a good idea (although it appears to be legal).
var Com = new React.createClass({
getInitialState: function() {
{ set: new Set() }
},
componentDidMount: function() {
let set = this.state.set;
set.add( this.props.someAttribute );
this.setState( { set: set } );
},
...
});
the reason this feels wrong is:
it appears React attempts to protects its component state, and
if so, probably uses Object.freeze()).
if these assumptions are correct, and knowing that Object.freeze() does not freeze Set() or Map(), it might be that using them undermines React's ability to protect its component state.
which is why the question: is this a good idea or not?

How to use setState() in React to blank/clear the value of an array

I am trying to clear an array, but I'm having trouble.
this.setState({warnErrorTypes:[]})
I'm not sure if I am dealing with a race condition, or what the specific issue is, but I can see that the value of my array is consistently wrong in the case that I need to reset its value to [].
How does one replace an array that contains [1,2] with [] then subsequently [3] where the following are true:
this.state.warnErrorTypes is an Array which starts out with []
Based on condition, 2 is pushed in Array
Based on condition, 1 is pushed in Array.
Based on condition, 3 is NOT pushed in Array
Pause. User interacts with UI
Array is blanked: this.setState({warnErrorTypes:[]})
Based on condition, 2 is NOT pushed in Array
Based on condition, 1 is NOT pushed in Array
Based on condition, 3 is pushed in Array.
The result of the logic above is always [2,1,3], when I expect it to be [3].
setState gets aggregated and scheduled, it does not run atomic and immediate, so you can't just issue multiple setState() calls and expect things to work, you either have to wait for the state to update before updating it again, or use an instance variable.
Option 1:
moo: function() {
this.setState({
myarr: []
}, function() { // called by React after the state is updated
this.setState({
myarr: [3]
});
});
}
This is fairly cumbersome and depending on what you're doing, mostly just bad code. The other option is to use a "real" instance variable that you send over as state at moments when you need to.
Option 2:
getInitialState: function() {
this.mylist = [];
return {
myarr: this.mylist
};
},
...
moo: function() {
this.mylist = [];
for(var i=0; i<10; i++) {
this.mylist.push(i);
}
this.setState({ myarr: this.mylist });
}
Remember that updating the state means you have changed an aspect of your component that warrants a rerender, so don't use setState if you don't intend the component to rerender, like between clearing the array and refilling it. Do that stuff separately, and only update the state once you're done.
Option 3:
You could also do this by taking out the state values, running your updates, and then rebinding, without ever building a persistant instance variable:
moo: function() {
var list = this.state.myarr;
while(list.length > 0) { list.splice(0,1); }
for(var i=0; i<10; i++) { list.push(i); }
this.setState({ myarr: list });
}
The net effect is the same: you only update your UI when your data is in some stable configuration, so if you think you're calling setState() more than once between renders, that's a problem: every setState() call may trigger a render "eventually", and consecutive setState() calls before that happens will "override" same-named-key value updates if you don't wait for them to bind first.
Option 3, as mentioned by Anders Ekdahl:
moo () {
this.setState(state => ({
myarr: []
}));
// ...
if (weShouldAddAThree) {
this.setState(state => ({
myarr: [ ...state.myarr, 3 ] // like push but without mutation
}));
}
}
This pattern is useful if you need to refer to the previous existing state when you perform your state update. I'm not sure if you really need the previous state in your example, but I will explain this pattern as if you did.
The merge operation we provide to setState is always applied asynchronously, at some point in the future, at React's discretion. When the setState() function returns, our operation has not been applied, it has only been queued.
Therefore we should never use this.state in our state updater, because that might be out of date: an old copy of the state. If we need to know the previous state, we should receive the state argument in the function we pass to setState, and use that.
You can as well use this to clear array:
this.state.your_array.length = 0;
setState({your_array});

React.js having state based on other state

I'm running into some problems with React.js and the state not being immediately set when calling setState(). I'm not sure if there are better ways to approach this, or if it really is just a shortcoming of React. I have two state variables, one of which is based on the other. (Fiddle of original problem: http://jsfiddle.net/kb3gN/4415/ you can see in the logs that it's not set right away when you click the button)
setAlarmTime: function(time) {
this.setState({ alarmTime: time });
this.checkAlarm();
},
checkAlarm: function() {
this.setState({
alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
});
}, ...
When calling setAlarmTime, since this.state.alarmTime isn't updated immediately, the following call to checkAlarm sets alarmSet based on the previous value of this.state.alarmTime and is therefore incorrect.
I solved this by moving the call to checkAlarm into the callback of setState in setAlarmTime, but having to keep track of what state is actually 'correct' and try to fit everything into callbacks seems ridiculous:
setAlarmTime: function(time) {
this.setState({ alarmTime: time }, this.checkAlarm);
}
Is there a better way to go about this? There are a few other places in my code which I reference state I just set and now I'm unsure as to when I can actually trust the state!
Thanks
Yes, setState is asynchronous, so this.state won't be updated immediately. Here are the unit tests for batching, which might explain some of the details.
In the example above, alarmSet is data computed from the alarmTime and elapsedTime state. Generally speaking, computed data shouldn't be stored in the state of the object, instead it should be computed as-needed as part of the render method. There is a section What Shouldn’t Go in State? at the bottom of the Interactivity and Dynamic UIs docs which gives examples of things like this which shouldn't go in state, and the What Components Should Have State? section explains some of the reasons why this might be a good idea.
As Douglas stated, it's generally not a good idea to keep computed state in this.state, but instead to recompute it each time in the component's render function, since the state will have been updated by that point.
However this won't work for me, as my component actually has its own update loop which needs to check and possibly update its state at every tick. Since we cannot count on this.state to have been updated at every tick, I created a workaround that wraps React.createClass and adds it's own internal state tracking. (Requires jQuery for $.extend) (Fiddle: http://jsfiddle.net/kb3gN/4448/)
var Utils = new function() {
this.createClass = function(object) {
return React.createClass(
$.extend(
object,
{
_state: object.getInitialState ? object.getInitialState() : {},
_setState: function(newState) {
$.extend(this._state, newState);
this.setState(newState);
}
}
)
);
}
}
For any components where you need up-to-date state outside of the render function, just replace the call to React.createClass with Utils.createClass.
You'll also have to change all this.setState calls with this._setState and this.state calls with this._state.
One last consequence of doing this is that you'll lose the auto-generated displayName property in your component. This is due to the jsx transformer replacing
var anotherComponent = React.createClass({
with
var anotherComponent = React.createClass({displayName: 'anotherComponent'.
To get around this, you'll just have to manually add in the displayName property to your objects.
Hope this helps
Unsure if this was the case when question was asked, though now the second parameter of this.setState(stateChangeObject, callback) takes an optional callback function, so can do this:
setAlarmTime: function(time) {
this.setState({ alarmTime: time }, this.checkAlarm);
},
checkAlarm: function() {
this.setState({
alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
});
}, ...

Categories