React: Array and Immutability Helpers - reactjs

I am trying to add an array to a state that contains an array. I wish to add the data to the end of the array. My data looks like this:
getInitialState: function() {
return {name: [{program: "File Manager"},
{program: "Add File"},
{program: "Index"},
{program: "File Editor"}],
program: "Index",
display: true};
},
and I wish to update the name variable to also contain something. For example add {program: "Text Editor"}
How could I go about doing this with this.setState()? Or, if not, is there another way to do this. I read about $push but couldn't really find any examples that helped me out. I wish to do this in componentDidMount:

Create a new array and assign it to the state:
var currentName = this.state.name;
var newName = currentName.concat([{program: "Text Editor"}]);
this.setState({name: newName});
(The first line isn't necessary, just included for clarity.)
You could also use the function version of setState:
this.setState(function(state) {
return { name: state.name.concat([{program: "Text Editor"}]) };
});
If you're in an environment that supports ES2015 arrows, you can do this in a pretty concise way:
this.setState(state => ({ name: state.name.concat([{program: "Text Editor"}]) });
Since concat already generates a new array, you don't need to use the immutability helpers, but if you want to, it'd look like this:
var newState = update(this.state, { // generate a new state object...
name: { $push: [ "TextEditor" ] } // with the item pushed onto the `name` key
});
It's possible to mutate an existing property of this.state and then re-set it with setState, e.g.
// warning: antipattern!
this.state.name.push("Text Editor");
this.setState({name: this.state.name});
but this is not considered a very good practice (you should always avoid directly modifying this.state):
Notes:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

You can read the this.state.name array, modifying it (by pushing to the array), then call setState again.

Related

react am I mutating my state in case it is not an object or array?

I know how To avoid mutating objects and arrays in react state, but I am not sure about variables that are not objects and arrays like this example
this.state = {
cubeNumber: 0,
}
onNumberChange = (event) => {
this.setState({ [event.target.name]: event.target.value })
}
cubeArrayRender = () => {
let { cubeNumber } = this.state;
am I mutating state by using parseInt like this?
let cubes = parseInt(cubeNumber, 10);
or if I write like this?
let cubes = cubeNumber;
cubes = 2;
If I am mutating state, how can I avoid it?
According to React documentation you shouldn't assign state like this
// Wrong
this.state.comment = 'Hello';
But only this:
// Correct
this.setState({comment: 'Hello'});
So yeah you are not mutating state in your code :)
You can mutate state only acting directly on this.state or through setState.
You are not mutating state (or anything) by using the method you have shown in your example. parseInt returns an Integer and does not modify the String/Number that you give it, instead creating a new instance.
As per #Steve Vaughan answer. In this case you are not mutating state because the state
you used for this test is value type. But object are reference type. While you assigning
the cubeNumber to new variable cube that makes the deep copy. But that's not same for objects, you are just passing reference.
For example:
//intial state
this.state = {
user: {name:"sarath"},
age:25
}
let age=this.state.age; //deep copy
age=35; // this not going affect the original state
let user=this.state.user //Shallow copy
user.name="kumar" //It mutates the original object also
output:
original age: 25
new age: 35
name: Kumar
new name: Kumar
you can avoid the mutation by using two methods
Object Assign
This method is used to copy the values of all enumerable own properties from one or more source objects to a target object.
let user= Object.Assign({},this.state.user); //deep copy of values
user.name="kumar"; // Here the original state is untouched.
Deep cloning of entire object
Object.Assign copies property values only. It won't copy the reference inside of the Object.
For example:
//intial state
this.state = {
user: {name:"sarath",interest:{music:"western",sports:"foot ball"},
age:25
}
let user=Object.Assign({},this.state.user);
user.name="Jasrin";
user.interest.sports="cricket";
console.log(this.state.user.name); //sarath
console.log(user.name); //kumar
console.log(this.state.user.interest.sports); //cricket
console.log(this.state.user.interest.sports); //cricket
Alternate way to do deep cloning is
let user=JSON.parse(JSON.stringify(this.state.user))

is this actually mutating state? A bad practice?? - react

this is a piece of code I came across and I am not sure what it is doing actually?
it feels to me like it is mutating the state
Version 1:
this.state.items = [...this.state.initialItems];
Now, is the above code mutating state??
And can the above also be written as Version 2:
this.setState({
items:[...this.state.initialItems];
});
the thing is that version 1 is working but version 2 isnt.
So,what's wrong here?
what am I missing?
what does ...this.state.initialItems do? is that the spread operator?
This code is definitely mutating state. Don't do it:
this.state.items = [...this.state.initialItems];
Yes, the ... is a spread operator in this context.
For version 2, make sure you don't use the semicolon.
It should just be :
this.setState({
items:[...this.state.initialItems]
});
And of course like the poster below has said, it's asynchronous so you won't see the results right away.
Augmentation 1:
If you want to do things after your state is ready, you can add a callback as your 2nd argument to setState.
this.setState({
items:[...this.state.initialItems]
}, ()=>{
// do stuff
}
);
Augmentation 2:
Your first argument to setState can be a function as well, as the other poster has mentioned, so that when you access state it will be the "freshest" copy, and not the one that was captured when you created the object that you passed to setstate:
this.setState((stateNOW) => {
return {items:[...stateNOW.initialItems]};
}, ()=>{
// do stuff
}
);
Version 1 is wrong. But the real question is Why?
state is a special object in React and it is not to be mutated.
Say you declare an object:
const obj = {
label: 'doe'
value: 'foo'
}
Then, you update the obj with
obj.value = 'bar'
This is the process of mutation and should never perform it with a state variable. Instead we use setState as a request to change the state.
... is the spread syntax.
As the other answers have mentioned, the reason why you think it is not working might be because state updates are asynchronous.
You have to use the setState callback or componentDidUpdate lifecycle hook to log the value of state.
setState(stateChange[, callback])
Another issue in the code is the use of this.state.something inside setState. Like we already mentioned before setState is asynchronous and a number of state updates may be merged. So using this.state.initialItems may not always work out for you, if you miss a update.
this.setState((prevState) => {
return {items: [...prevState.initialItems]};
});
This would be the right way to use a value of previous state.
Version 1 is mutating your state and this is a bad practice.
Version 2 is not and the reason why it's not working (actually it is) is that calls to setState are asynchronous and therefore you will not have access to its up-to-date value in the next line.
Should you need access to the new state value, you can use:
this.setState(prevState => ({
items: [...prevState.initialItems],
}), () => {
console.log(this.state) // new state value
})
Please also refer to this link in ReactJS docs about how to set state properly.
The ... is indeed the spread syntax.
You can use it, for example:
To add items to an array
const arr = [1, 2, 3, 4]
const newArr = [...arr, 5, 6] // This will spread everything in arr and add numbers 5 and 6 to newArr
console.log('arr', arr)
console.log('newArr', newArr)
To get items from an array
const arr = [1,2,3,4]
const [firstItem, secondItem, ...others] = arr
console.log('firstItem:', firstItem)
console.log('secondItem:', secondItem)
console.log('others:', others)
To create a new object
const obj = {
name: 'Stack Overflow',
age: 10,
}
const newObj = {
...obj, // gets all properties from obj
age: 12, // but only change age
}
console.log('obj', obj)
console.log('newObj', newObj)

React set state variable

I've an confusion. I'm trying to add array variable in setState. My code is working properly but wanted to confirm some doubt before committing my code.
Which is right way to store array in state variable ?
var names = ['Jake', 'Jon', 'Thruster'];
this.setState({
state: names
});
Or
this.setState((state) => {
state.items.push(names[0]);
return state;
});
What is necessary of return statement here ?
Can some one please explain me the difference here ? I searched in google but I'm still confused.
var names = ['Jake', 'Jon', 'Thruster'];
this.setState({
names //according to Airbnb rules
});
or
this.setState({
names: names
});
this.state.names = ['Jake', 'Jon', 'Thruster'];
setState takes a second argument - callback, that will called after setting State properties
setState({property1: value1, ...}, () => { //some code after State changed })
The first approach is much more common and in my opinion, easier to read. The issue with your code is you don't need to use the key state, because the function you are calling is "setting state". The key should be something like firstNames.
this.setState({
firstNames: names
});
You could also just pass the object in the function like this because setState takes an object as a parameter.
var namesObject = {
firstNames: names
}
this.setState(namesObject);
I would read more about it here and keep doing small tutorials on this an you'll get the hang of it.
https://facebook.github.io/react/docs/react-component.html#setstate

Cannot Update State in React.js

I am using React with ES2015. I have this in my state:
this.state = { info: {
merchandise: {
label: '',
visible: false
},
hotels: {
label: '',
visible: false
}
}
}
I'm try to update state with this code:
this.setState({info.merchandise.label: "New Label"})
but I get an error whereas I can log the value of this.state.info.merchandise.label without any issues. What am I doing wrong?
I believe you can only use setState() on a direct property of this.state.
What you could do in this situation is
Make a shallow copy of state property you want to work with
Change the property you want changed in the copy.
The copy now has the updated properties you wanted. So assign it back to this.state
Like so:
let temp = Object.assign({}, this.state.info);
temp.merchandise.label = "New Label";
this.setState(info: temp);
You can do following,
let newState = this.state;
newState.info.merchandise.label = "New Label";
this.setState(newState);
According to React's setState doc:
Performs a shallow merge of nextState into current state
This means you should provide a valid nextState object as argument to the method. {info.merchandise.label: "New Label"} is not syntactically correct, that's why you get an error.
Treat this.state as if it were immutable.
This simply means that if you want to change some property inside the state object, you should not mutate it directly, but replace it with a new object containing the modification instead.
In your case, the state object contains several nested objects. If you want to modify a "leaf" object's property, you'll also have to provide new objects for every containing object up the tree (because each of them have a property that changes in the process). Using Object.assign() to create new objects, you could write:
// new "leaf" object:
const newMerchandise = Object.assign({}, this.state.info.merchandise,
{label: "New Label"});
// new "parent" object:
const newInfo = Object.assign({}, this.state.info,
{merchandise: newMerchandise});
// new state ("parent") object:
const newState = Object.assign({}, this.state,
{info: newInfo});
// eventually:
this.setState(newState);
By the way, you'll notice that it's easier to handle the state object when its structure tends to be "flat" rather than "deeply nested".

How to avoid complex initial state declaration in React

If I'm, using complex object Structure in React render, how can I avoid redefining that structure in getInitialState method
var UserGist = React.createClass({
getInitialState: function() {
return {
user:{
section: {
country: 'Iran'
}
},
lastGistUrl: ''
};
},
....
....
render: function() {
return (
<div>
{this.state.user.section.country}'s Amex Initial
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
Now the problem is the actual structure of the object used is pretty huge
user:{
section: {
.....
25 levels of nesting
.....{
country: 'Iran'
}
}
}
and that object is coming from backend , so how can I avoid defining the entire object structure in getInitialState()
First of all, state should be shallow. You shouldn't have deep objects as props or state.
Next, why do you even want to do this? Is it so that you don't get a bunch of "xx is not defined" errors on the initial render? If so, why don't you just declare user: {} in getInitialState, and wrap an if (!_.isEmpty(this.state.user)) condition around your render code?
Or, since the data is coming from the server, maybe it's a good thing to re-declare this structure. You can use getInitialState to create placeholder data until the real object shows up.
Also, be aware that setState only does a shallow replacement. If you change any property or sub-property of the user object, you'll need to clone it, change the property, and then call setState({user: clonedAndUpdatedUser}). Or, just call forceUpdate.
You should really just try to get your props and state to be shallow.
Good luck!

Resources