I'm simply trying to append a json created dynamically into an array that lives inside the state.
The only way that I've found till now is to use concat, because push doesn't work.
constructor() {
super()
this.state = {
list: []
}
this.add = this.add.bind(this)
}
add(e) {
e.preventDefault()
var task = document.getElementById('task').value
var json = JSON.parse(JSON.stringify({key: task, item: task}))
this.setState({list: this.state.list.concat(json)}) // WORKS
// this.setState({list: this.state.list.push(json)}) // DOESN'T WORK
console.log(this.state.list)
document.getElementById('task').value = ''
}
That works, but I don't understand why I have an empty array in console after the first item it's created. I've read how concat works, but obviously I missed something. Why I can't push an object inside my this.list array?
The concat otherwise create a new array and return it.
The push returns the new length of the array, which means a number, which means NOT what you want to set to state.list. That's why it didn't work.
why I have an empty array in console
From the docs: 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. That's why you are getting the empty array in the console.
And one more thing, don't mutate your state directly. If you call state.list.push(...), your changes will not be maintained by React and will be discarded if you call setState later. So it's a good thing to use concat.
this.state is immutable and has to be changed only via the setState() function.
From the docs:
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.
So, the best way to do this is to replace the array in this.state by a new array, and not try to push/modify it.
var newState = Object.assign({}, this.state); // Clone the state obj in newState
newState[arrayKey].push(newItem); // modify newState
this.setState(newState); // setState()
Array.prototype.push() returns the new length property of the object upon which the method was called.
See:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/push#Returns
Related
I have the following code :-
const [dice, setDice] = React.useState(allNewDice()) // Array of objects with the following properties: `value` (random digit between 1 and 6), `id` (unique id), `isHeld` (boolean)
function holdDice(id) {
const heldDie = dice.findIndex(die => die.id === id)
setDice(prevDice => {
prevDice[heldDie].isHeld = !prevDice[heldDie].isHeld
return prevDice
})
}
The function holdDice is working as expected. It updates prevDice's first element's isHeld property to the opposite of what was previously, as evident from logging it to the console. But the only problem is, the state is not getting updated.
So I tried making a copy of prevDice and then returning the copy (using the spread (...) operator) :-
const [dice, setDice] = React.useState(allNewDice()) // Array of objects with the following properties: `value` (random digit between 1 and 6), `id` (unique id), `isHeld` (boolean)
function holdDice(id) {
const heldDie = dice.findIndex(die => die.id === id)
setDice(prevDice => {
prevDice[heldDie].isHeld = !prevDice[heldDie].isHeld
return [...prevDice]
})
}
And this works perfectly.
Can anyone please explain why this behaviour? Why can't I return the previous state passed in to be set as the new state, even when I am not returning the previous state, exactly as it was?
I understand that it's expensive to re-render the component and that maybe React does not update the state when the previous and the new states are equal. But here, the previous and the new states are not equal.
My first guess was that it might have been related to React not deep checking whether the objets of the array prevDice have been changed or not, or something like that. So I created an array of Numbers and tried changing it's first element to some other number, but unless i returned a copy of the array, the state still did not change.
Any help would be greatly appreciated. Thank you!
The first time you call setDice() after the initial mount of your component or rerender, prevDice refers to the same array in memory that your dice state refers to (so prevDice === dice). It is not a unqiue copy of the array that you're able to modify. You can't/shouldn't modify it because the object that prevDice refers to is the same object that dice refers to, so you're actually modifying the dice state directly when you change prevState. Because of this, when the modified prevDice is used as the value for setDice(), React checks to see if the new state (which is prevState) is different from the current state (dice) to see if it needs to rerender. React does this equality check by using === (or more specifically Object.is()) to check if the two arrays/objects are the same, and if they are, it doesn't rerender. When React uses === between prevState and dice, JS checks to see if both variables are referring to the same arrays/objects. In our case they are, so React doesn't rerender. That's why it's very important to treat your state as immutable, which you can think of as meaning that you should treat your state as readonly, and instead, if you want to change it, you can make a "copy" of it and change the copy. Doing so will correctly tell React to use that new modified copy as the new state when passed into setDice().
Do note that while your second example does work, you are still modifying your state directly, and this can still cause issues. Your second example works because the array you are returning is a new array [], but the contents of that array (ie: the object references) still refers to the same objects in memory that your original dice state array referred to. To correctly perform your update in an immutable way, you can use something like .map(), which by nature returns a new array (so we'll rerender). When you map, you can return a new inner object when you find the one you want to update (rather than updating it directly). This way, your array is a new array, and any updated objects are also new:
function holdDice(id) {
setDice(prevDice => prevDice.map( // `.map()` returns a new array, so different to the `dice`/`prevDice` state
die => die.id === id
? {...die, isHeld: !die.isHeld} // new object, with overwritten property `isHeld`
: die
));
}
State doesn't update because you update it yourself in the setDice function.
dice and prevDice refer to the same object so it won't trigger a re-render.
const [dice, setDice] = React.useState(allNewDice())
function holdDice(id) {
const heldDie = dice.findIndex(die => die.id === id)
setDice(prevDice => {
// Create a copy of the dice
// Use whatever method you wish.Spread. JSON parse and stringify.
const currentDice = JSON.parse(JSON.stringify(prevDice))
currentDice[heldDie].isHeld = !prevDice[heldDie].isHeld
return currentDice
})
}
I just start picking up react.js so I went through a lot of tutorials and I've stumbled upon this bit which basically meant to delete an item from the state.
this is how the guy introduced to me the delete function
delTodo = id => {
this.setState({
todos: [...this.state.todos.filter(todo => todo.id !== id)]
});
};
Since I am not so familiar with javascript I had a hard time figuring out what the ... operator is doing and why exactly is he using it in the given scenario. So in order to have a better understanding of how it works, I played a bit in the console and I've realised that array = [...array]. But is that true?
Is this bit doing the same exact thing as the one from above?
delTodo = id => {
this.setState({
todos: this.state.todos.filter(todo => todo.id !== id)
});
};
Could someone more experienced clarify to me why he has chosen to be using that approach instead of the one I've come up with?
As per the documentation:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
reactjs.org/docs/react-component.html#state
So, in the example from the tutorial you've mentioned, you wouldn't need to make a copy of the array to update your state.
// GOOD
delTodo = id => {
this.setState({
todos: this.state.todos.filter(...)
})
}
Array.filter method creates a new array and does not mutate the original array, therefore it won't directly mutate your state. Same thing applies to methods such as Array.map or Array.concat.
If your state is an array and you're applying methods that are mutable, you should copy your array.
See more to figure out which Array methods are mutable:
doesitmutate.xyz
However, if you were to do something like the following:
// BAD
delTodo = id => {
const todos = this.state.todos
todos.splice(id, 1)
this.setState({ todos: todos })
}
Then you'd be mutating your state directly, because Array.splice changes the content of an existing array, rather than returning a new array after deleting the specific item. Therefore, you should copy your array with the spread operator.
// GOOD
delTodo = id => {
const todos = [...this.state.todos]
todos.splice(id, 1)
this.setState({ todos: todos })
}
Similarly with objects, you should apply the same technique.
// BAD
updateFoo = () => {
const foo = this.state.foo // `foo` is an object {}
foo.bar = "HelloWorld"
this.setState({ foo: foo })
}
The above directly mutates your state, so you should make a copy and then update your state.
// GOOD
updateFoo = () => {
const foo = {...this.state.foo} // `foo` is an object {}
foo.bar = "HelloWorld"
this.setState({ foo: foo })
}
Hope this helps.
Why use the spread operator at all?
The spread operator ... is often used for creating shallow copies of arrays or objects. This is especially useful when you aim to avoid mutating values, which is encouraged for different reasons. TLDR; Code with immutable values is much easier to reason about. Long answer here.
Why is the spread operator used so commonly in react?
In react, it is strongly recommended to avoid mutation of this.state and instead call this.setState(newState). Mutating state directly will not trigger a re-render, and may lead to poor UX, unexpected behavior, or even bugs. This is because it may cause the internal state to differ from the state that is being rendered.
To avoid manipulating values, it has become common practice to use the spread operator to create derivatives of objects (or arrays), without mutating the original:
// current state
let initialState = {
user: "Bastian",
activeTodo: "do nothing",
todos: ["do nothing"]
}
function addNewTodo(newTodo) {
// - first spread state, to copy over the current state and avoid mutation
// - then set the fields you wish to modify
this.setState({
...this.state,
activeTodo: newTodo,
todos: [...this.state.todos, newTodo]
})
}
// updating state like this...
addNewTodo("go for a run")
// results in the initial state to be replaced by this:
let updatedState = {
user: "Bastian",
activeTodo: "go for a run",
todos: ["do nothing", "go for a run"]
}
Why is the spread operator used in the example?
Probably to avoid accidental state mutation. While Array.filter() does not mutate the original array and is safe to use on react state, there are several other methods which do mutate the original array, and should not be used on state. For example: .push(), .pop(),.splice(). By spreading the array before calling an operation on it, you ensure that you are not mutating state. That being said, I believe the author made a typo and instead was going for this:
delTodo = id => {
this.setState({
todos: [...this.state.todos].filter(todo => todo.id !== id)
});
};
If you have a need to use one of the mutating functions, you can choose to use them with spread in the following manner, to avoid mutating state and potentially causing bugs in your application:
// here we mutate the copied array, before we set it as the new state
// note that we spread BEFORE using an array method
this.setState({
todos: [...this.state.todos].push("new todo")
});
// in this case, you can also avoid mutation alltogether:
this.setState({
todos: [...this.state.todos, "new todo"]
});
As .filter gives you a new array (than mutating the source array), it is acceptable and results in the same behaviour, making spreading redundant here.
What's not acceptable is:
const delIndex = this.state.todos.findIndex(todo => todo.id !== id);
this.state.todos.splice(delIndex, 1); // state mutation
this.setState({
todos: this.state.todos
});
slice is fine though:
const delIndex = this.state.todos.findIndex(todo => todo.id !== id);
this.setState({
todos: [
...this.state.todos.slice(0, delIndex),
...this.state.todos.slice(delIndex + 1)
]
});
If you mutate state (which keeps ref same) React may not be able to ascertain which part of your state actually changed and probably construct a tree on next render which is different from expected.
The spread operator that the guy's code is applying causes the array returned from the filter function to be copied again.
Since [.filter is returning a new array][https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter], you will already avoid mutating the array in state directly. It seems like the spread operator may be redundant.
One thing I wanted to point out too is while the values in a copied array may be the same as the values in an old array (array = [...array]), the instances change so you wouldn't be able to use '===' or '==' to check for strict equivalency.
const a = ['a', 'b']
const b = [...a]
console.log(a === b) // false
console.log(a == b) // false
console.log(a[0] === b[0]) // true
console.log(a[1] === b[1]) // true
Hope this helps!
I don't know if there is a conflict in the libraries I'm using but for some reason or its, the latest react but when I'm updating an array from the state, it's mutating it even though I haven't called setState. Is there a special case for Arrays in the state that I'm not aware of?
constructor(props){
super(props);
this.state = {
data: [
{ id: 1, name: 'hello' },
{ id: 2, name: 'world' },
{ id: 3, name: 'once' }
]
}
}
performUpdate() {
const { data } = this.state;
var current_data = data.slice();
current_data[0].name = 'helloooooo';
console.log(data)
// it shows data.id 1 now has name: 'helloooooo'
}
Since I made a copy of the array from state, shouldn't the state be updated only if I call
this.setState({
data: current_data
})
Since I made a copy of the array from state, shouldn't the state be
updated only if I call
You made a shallow copy. This:
current_data[0].name
is same as saying:
this.state.data[0].name
this.state.data[0] and current_data[0] point to same object, due to shallow copy; even though slice returned new array, its elements basically point to same objects as the elements from the original array.
You maybe interested in how to immutably modify one of array elements.
slice performs a shallow copy of the array - meaning that only the pointers to the objects contained in the array are copied, not the objects themselves.
This means that after the slice you have two arrays pointing at the same objects. Naturally, then, changing an object in one, will change it in the other.
Instead, what you want to do is create a deep clone. You can use immutability-helper or something like Lodash to achieve this.
In this pen, I am having a hard time understanding why when using the push function on a newly created const (line 24) it matters whether one uses the spread operator or simply copies "state" over. How is it that replacing [...state] with state causes state to be overwritten with the content of newState?
Am I correct in assuming that it is the reference to state which is overwritten if state is directly passed to newState? I.e. in the case of const newState = state
I assume this is the section or the pen you are referencing:
const newState = [...state]; // copy contents of state, NOT the reference to it (state)
newState.push(action.payload);
return newState;
Let's look at the difference between what this code is doing now, and what it would do if const newState = state was used instead.
const newState = [...state];
This line creates a new array, called newState with the contents of state. It is essentially a one liner for:
const newState = [];
for (let i = 0; i < state.length; i++) {
newState.push(state[i]);
}
When newState.push(action.payload) is called, the state array is unchanged, while newState has an extra element pushed into it.
const newState = state;
This line sets the variable newState to the same reference as state, so there is only one array in play.
When newState.push(action.payload) is called, the new element is pushed into the array, so both newState and state have changed.
Using the spread operator like this will take all the items from state and create a new array with these items. So state and newState will reference different arrays (which have the same elements, so if one of these elements is an object and you change one of it's properties, that change will be visible in the other array). Writing const newState = state will result in one array and two references to that array, since you're not creating a second array anywhere.
We are having a heated discussion on how to update nested state in React.
Should the state be immutable or not? What is the best practice to update the state gracefully?
Say you have state structure that looks like this:
this.state = {
numberOfStudents: "3",
gradeLevel: "5",
students : [
{ id : "1234",
firstName: "John",
lastName: "Doe",
email: "johndoe#mail.com"
phoneNumer: "12345"
},
{ id : "56789",
firstName: "Jane",
lastName: "Doe",
email: "janedoe#mail.com"
phoneNumer: "56789"
},
{ id : "11111",
firstName: "Joe",
lastName: "Doe",
email: "joedoe#mail.com"
phoneNumer: "11111"
}
]
}
Then we want to update joe doe's phone number.
A couple of ways we can do it:
mutate state + force update to rerender
this.state.students[2].phoneNumber = "9999999";
this.forceUpdate();
mutate state + setState with mutated state
this.state.students[2].phoneNumber = "9999999";
this.setState({
students: this.state.students
});
Object.assign, this still mutate the state since newStudents is just a new reference to the same object this.state points to
const newStudents = Object.assign({}, this.state.students);
newStudents[2].phoneNumber = "9999999"
this.setState({
students: newStudents
});
Update immutability helper (https://facebook.github.io/react/docs/update.html) + setState. This can get ugly very quickly if we have address.street, address.city, address.zip in each student object and want to update the street.
const newStudents = React.addons.update(this.state.students, {2: {phoneNumber: {$set:"9999999"}}});
this.setState({
students: newStudents
})
Last line of the react doc for setState states that :
Never mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
https://facebook.github.io/react/docs/react-component.html
The docs states that we shouldn't use forceUpdate to rerender:
Normally you should try to avoid all uses of forceUpdate() and only
read from this.props and this.state in render().
Why is this the case, what can happen if we mutate state and call setState afterward? Under what circumstances will setState() replace the mutation we made? This is a very confusing statement. Can someone please explain the possible complication of each of the scenario we are using above to set the state.
You state that:
"Object.assign, this still mutate the state since newStudents is just a new reference to the same object this.state points to"
This statement is incorrect.Object.assign mutates the state passed in to its first parameter. Since you pass in an empty object literal ({}), you are mutating the new object literal and not this.state.
Some background:
The principle of Immutable state has connections with Functional programming.
It is useful in React because it provides a way for React to know if
the state has changed at all, one use case it is useful is for optimising when components should re-render
Consider the case of a complex state with nested objects.
Mutating the state's values would alter the values of properties within the state but it would not change the object's reference.
this.state = {nestObject:{nestedObj:{nestObj:'yes'}}};
// mutate state
this.state.nestObject.nestedObj.nestObj= 'no';
How do we know if React should re-render the component?
A deep equality check? Imagine what this would look like in a complex state, that's hundreds, even thousands of checks per state update...
No need to check for changes, just force React to re-render everything with every state change...
Could there be an alternative to the latter two approaches?
The Immutable way
By creating a new object (and therefore a new reference), copying over the old state with Object.assign and mutating it, you get to manipulate the state values and change the object reference.
With the Immutable state approach we can then know if the state has changed by simply checking if the object references are equal.
An simplified example for the naysayers in the comments below:
Consider this simple example:
this this.state = { someValue: 'test'}
var newState = Object.assign({}, this.state);
console.log(newState); // logs: Object {someValue: "test"]
console.log(this.state); // logs: Object {someValue: "test"]
// logs suggest the object are equal (in property and property value at least...
console.log(this.state === this.state); // logs: true
console.log(this.state === newState); // logs: false. Objects are
// pass-by-reference, the values stored
// stored in this.state AND newState
// are references. The strict equality
// shows that these references
// DON'T MATCH so we can see
// that an intent to modify
// state has been made