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.
Related
I understand that React tutorials and documentation warn in no uncertain terms that state should not be directly mutated and that everything should go through setState.
I would like to understand why, exactly, I can't just directly change state and then (in the same function) call this.setState({}) just to trigger the render.
E.g.: The below code seems to work just fine:
const React = require('react');
const App = React.createClass({
getInitialState: function() {
return {
some: {
rather: {
deeply: {
embedded: {
stuff: 1,
},
},
},
},
},
};
updateCounter: function () {
this.state.some.rather.deeply.embedded.stuff++;
this.setState({}); // just to trigger the render ...
},
render: function() {
return (
<div>
Counter value: {this.state.some.rather.deeply.embedded.stuff}
<br></br>
<button onClick={this.updateCounter}>Increment</button>
</div>
);
},
});
export default App;
I am all for following conventions but I would like to enhance my further understanding of how ReactJS actually works and what can go wrong or is it sub-optimal with the above code.
The notes under the this.setState documentation basically identify two gotchas:
That if you mutate state directly and then subsequently call this.setState this may replace (overwrite?) the mutation you made. I don't see how this can happen in the above code.
That setState may mutate this.state effectively in an asynchronous / deferred way and so when accessing this.state right after calling this.setState you are not guaranteed to access the final mutated state. I get that, by this is not an issue if this.setState is the last call of the update function.
This answer is to provide enough information to not change/mutate the state directly in React.
React follows Unidirectional Data Flow. Meaning, the data flow inside react should and will be expected to be in a circular path.
React's Data flow without flux
To make React work like this, developers made React similar to functional programming. The rule of thumb of functional programming is immutability. Let me explain it loud and clear.
How does the unidirectional flow works?
states are a data store which contains the data of a component.
The view of a component renders based on the state.
When the view needs to change something on the screen, that value should be supplied from the store.
To make this happen, React provides setState() function which takes in an object of new states and does a compare and merge(similar to object.assign()) over the previous state and adds the new state to the state data store.
Whenever the data in the state store changes, react will trigger an re-render with the new state which the view consumes and shows it on the screen.
This cycle will continue throughout the component's lifetime.
If you see the above steps, it clearly shows a lot of things are happening behind when you change the state. So, when you mutate the state directly and call setState() with an empty object. The previous state will be polluted with your mutation. Due to which, the shallow compare and merge of two states will be disturbed or won't happen, because you'll have only one state now. This will disrupt all the React's Lifecycle Methods.
As a result, your app will behave abnormal or even crash. Most of the times, it won't affect your app because all the apps which we use for testing this are pretty small.
And another downside of mutation of Objects and Arrays in JavaScript is, when you assign an object or an array, you're just making a reference of that object or that array. When you mutate them, all the reference to that object or that array will be affected. React handles this in a intelligent way in the background and simply give us an API to make it work.
Most common errors done when handling states in React
// original state
this.state = {
a: [1,2,3,4,5]
}
// changing the state in react
// need to add '6' in the array
// bad approach
const b = this.state.a.push(6)
this.setState({
a: b
})
In the above example, this.state.a.push(6) will mutate the state directly. Assigning it to another variable and calling setState is same as what's shown below. As we mutated the state anyway, there's no point assigning it to another variable and calling setState with that variable.
// same as
this.state.a.push(6)
this.setState({})
Many people do this. This is so wrong. This breaks the beauty of React and is bad programming practice.
So, what's the best way to handle states in React? Let me explain.
When you need to change 'something' in the existing state, first get a copy of that 'something' from the current state.
// original state
this.state = {
a: [1,2,3,4,5]
}
// changing the state in react
// need to add '6' in the array
// create a copy of this.state.a
// you can use ES6's destructuring or loadash's _.clone()
const currentStateCopy = [...this.state.a]
Now, mutating currentStateCopy won't mutate the original state. Do operations over currentStateCopy and set it as the new state using setState().
currentStateCopy.push(6)
this.setState({
a: currentStateCopy
})
This is beautiful, right?
By doing this, all the references of this.state.a won't get affected until we use setState. This gives you control over your code and this'll help you write elegant test and make you confident about the performance of the code in production.
To answer your question,
Why can't I directly modify a component's state?
Well, you can. But, you need to face the following consequences.
When you scale, you'll be writing unmanageable code.
You'll lose control of state across components.
Instead of using React, you'll be writing custom codes over React.
Immutability is not a necessity because JavaScript is single threaded, but it's a good to follow practices which will help you in the long run.
PS. I've written about 10000 lines of mutable React JS code. If it breaks now, I don't know where to look into because all the values are mutated somewhere. When I realized this, I started writing immutable code. Trust me! That's the best thing you can do it to a product or an app.
The React docs for setState have this to say:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate(). If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
Basically, if you modify this.state directly, you create a situation where those modifications might get overwritten.
Related to your extended questions 1) and 2), setState() is not immediate. It queues a state transition based on what it thinks is going on which may not include the direct changes to this.state. Since it's queued rather than applied immediately, it's entirely possible that something is modified in between such that your direct changes get overwritten.
If nothing else, you might be better off just considering that not directly modifying this.state can be seen as good practice. You may know personally that your code interacts with React in such a way that these over-writes or other issues can't happen but you're creating a situation where other developers or future updates can suddenly find themselves with weird or subtle issues.
the simplest answer to "
Why can't I directly modify a component's state:
is all about Updating phase.
when we update the state of a component all it's children are going to be rendered as well. or our entire component tree rendered.
but when i say our entire component tree is rendered that doesn’t mean that the entire DOM is updated.
when a component is rendered we basically get a react element, so that is updating our virtual dom.
React will then look at the virtual DOM, it also has a copy of the old virtual DOM, that is why we shouldn’t update the state directly, so we can have two different object references in memory, we have the old virtual DOM as well as the new virtual DOM.
then react will figure out what is changed and based on that it will update the real DOM accordingly .
hope it helps.
It surprises me that non of the current answers talk about pure/memo components (React.PureComponent or React.memo). These components only re-render when a change in one of the props is detected.
Say you mutate state directly and pass, not the value, but the over coupling object to the component below. This object still has the same reference as the previous object, meaning that pure/memo components won't re-render, even though you mutated one of the properties.
Since you don't always know what type of component you are working with when importing them from libraries, this is yet another reason to stick to the non-mutating rule.
Here is an example of this behaviour in action (using R.evolve to simplify creating a copy and updating nested content):
class App extends React.Component {
state = { some: { rather: { deeply: { nested: { stuff: 1 } } } } };
mutatingIncrement = () => {
this.state.some.rather.deeply.nested.stuff++;
this.setState({});
}
nonMutatingIncrement = () => {
this.setState(R.evolve(
{ some: { rather: { deeply: { nested: { stuff: n => n + 1 } } } } }
));
}
render() {
return (
<div>
Normal Component: <CounterDisplay {...this.state} />
<br />
Pure Component: <PureCounterDisplay {...this.state} />
<br />
<button onClick={this.mutatingIncrement}>mutating increment</button>
<button onClick={this.nonMutatingIncrement}>non-mutating increment</button>
</div>
);
}
}
const CounterDisplay = (props) => (
<React.Fragment>
Counter value: {props.some.rather.deeply.nested.stuff}
</React.Fragment>
);
const PureCounterDisplay = React.memo(CounterDisplay);
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/ramda#0/dist/ramda.min.js"></script>
<div id="root"></div>
To avoid every time to create a copy of this.state.element you can use update with $set or $push or many others from immutability-helper
e.g.:
import update from 'immutability-helper';
const newData = update(myData, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}}
});
setState trigger re rendering of the components.when we want to update state again and again we must need to setState otherwise it doesn't work correctly.
My current understanding is based on this and this answers:
IF you do not use shouldComponentUpdate or any other lifecycle methods (like componentWillReceiveProps, componentWillUpdate, and componentDidUpdate) where you compare the old and new props/state
THEN
It is fine to mutate state and then call setState(), otherwise it is not fine.
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.
I am unclear about the use of this.state in React components. While I can create this.state.myvar, why should not I just create this.myvar?
class MyComponent extends Component {
state = {myvar: 123};
render() {
return <span>{this.state.myvar}</span>;
}
}
or
class MyComponent extends Component {
myvar = 123;
render() {
return <span>{this.myvar}</span>;
}
}
I realize that there are helpers like this.setState, but at the end this.state is just a convenience, right? Or does it play a bigger role in React? Should I avoid setting properties directly on this to store my state? If so, why?
No, in fact; rather wrong.
this.state in a react component is a special React-backed container that is only acknowledged as having been updated when you use setState, which triggers a re-render, which might cause DOM updates (or not, depending on what React's diff algorithm sees happening in the JS virtual dom).
You can, of course, also use object properties bound to this, but changing them does absolutely nothing for the component itself. React doesn't look at your full component instance for changes, it only looks (internally) at the state, and (when changed by parents) at the props.
As such, you can't "create" things like this.state.myvar and then expect them to actually exist from lifecycle function to lifecycle function: as a special management construct, any values you tack onto state outside of proper this.setState(...) calls have undefined behaviour. They might exist, or they might not. If you really are working with the internal state, then you need to signal changes via this.setState({ myvar: value }).
Of course, that doesn't mean you can't use this.myvar, that'll work fine, but changing it will not "do" anything other than literally just that.
When would you use this.val instead of state? When your component has to perform operations that lead to an "intermediate" state, neither being one renderable state, nor the next. For instance, when code can update a state value multiple times between renders, during those changes your component is in an intermediate state, and so you don't want it to re-render. In fact, expecting it to can lead to huge bugs:
...
doTest() {
this.setState({ val: this.state.val+1 });
this.setState({ val: this.state.val+1 });
this.setState({ val: this.state.val+1 });
},
...
This code will not yield a this.state.val that's 3 higher than before, because state updates are queued, and overwrite each other. Only the last instruction before a re-render "wins". So in that case you'd need something like:
...
doTest() {
var localval = this.state.val;
localval++;
localval++;
localval++;
this.setState({ val: localval });
},
...
And then if we also need that value accessible outside of this function, then we finally have a legitimate use for a this property:
...
doTest() {
this.accessibleval = this.state.val;
this.updateValueAFewTimes();
this.setState({ val: this.accessibleval });
},
...
this.state plays a larger role than you realize.
Setting state via this.setState triggers the component to re-render (among other things). Otherwise, React would have no way of knowing when something it depends on changed.
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.
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
});
}, ...