Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 902
Andrew Avatar asked Aug 05 '14 18:08

Andrew


People also ask

Is state in React synchronous or asynchronous?

State updates in React are asynchronous because rendering is an expensive operation and making state updates synchronous may cause the browser to become unresponsive.

What is remote state in React?

Remote state. Nearly every React app fetches data from a server via an HTTP call to a web service. This is remote state, because it originates from a server. Many apps store this state as local state, or via context if it's used globally.

What is Statein React?

What Is 'State' in ReactJS? The state is a built-in React object that is used to contain data or information about the component. A component's state can change over time; whenever it changes, the component re-renders.


2 Answers

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.

like image 108
Douglas Avatar answered Sep 29 '22 11:09

Douglas


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

like image 33
Andrew Avatar answered Sep 29 '22 11:09

Andrew