I have a state like this
const [tmp,setTmp] = useState(
{status:false},
{status:false},
{status:false},
{status:false}
)
how can I change status in tmp[2] ?
You can provide a completely new list of values with the help of the spread syntax to set the new value with a different reference.
// Simply shallow copies the existing list. This is simply to make a new reference.
const newTmp = [...tmp]
// The object references inside aren't changed. But React will rerender children components anyway when the bit that uses `tmp` is rerendered.
newTmp[2].status = true
// You can also do this to get a new reference to the object
newTmp[2] = {status: true}
setTmp(newTmp)
The reason you want to provide a new value with a different reference is per React's requirement: https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
If you simply do the following, it is not adequate since the reference to the array remains the same and React does not do deep equality check to find out if the new value is different.
tmp[2].status = true
setTmp(tmp) // Does not adhere to React docs and will probably fail at rerendering
P.S. Among the JS data types, Array, Function, and Object are references. Do read up on checking equality with references if this isn't familiar to you.
As long as the array is a different reference to the existing, React will detect this as a change. So you can clone the array, and then update the item at the index like shown by Daniel.
Or you can even use the normal JS array method slice to build a new array.
eg.
setTmp([...tmp.slice(0, 2), {status: true}, ...tmp.slice(3)]);
If you do this a lot, you could convert the above into a simple helper function.
function changeArrayItem(arr, ix, value) {
return [...arr.slice(0, ix), value, ...arr.slice(ix +1)];
}
//use
setTemp(changeArrayItem(tmp, 2, {status:true});
Related
Ever since its introduction in ECMA-262, 3rd Edition, the Array.prototype.push method's return value is a Number:
15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )
The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.
What were the design decisions behind returning the array's new length, as opposed to returning something potentially more useful, like:
A reference to the newly appended item/s
The mutated array itself
Why was it done like this, and is there a historical record of how these decisions came to be made?
I understand the expectation for array.push() to return the mutated array instead of its new length. And the desire to use this syntax for chaining reasons.
However, there is a built in way to do this: array.concat().
Note that concat expects to be given an array, not an item. So, remember to wrap the item(s) you want to add in [], if they are not already in an array.
newArray = oldArray.concat([newItem]);
Array chaining can be accomplished by using .concat(), as it returns an array,
but not by .push(), as it returns an integer (the new length of the array).
Here is a common pattern used in React for changing the state variable, based on its prior value:
// the property value we are changing
selectedBook.shelf = newShelf;
this.setState((prevState) => (
{books: prevState.books
.filter((book) => (book.id !== selectedBook.id))
.concat(selectedBook)
}
));
state object has a books property, that holds an array of book.
book is an object with id, and shelf properties (among others).
setState() takes in an object that holds the new value to be assigned to state
selectedBook is already in the books array, but its property shelf needs to be changed.
We can only give setState a top level object, however.
We cannot tell it to go find the book, and look for a property on that book, and give it this new value.
So we take the books array as it were.
filter to remove the old copy of selectedBook.
Then concat to add selectedBook back in, after updating its shelf property.
Great use case for wanting to chain push.
However, the correct way to do this is actually with concat.
Summary:
array.push() returns a number (mutated array's new length).
array.concat([]) returns a new array.
Technically, it returns a new array with the modified element added to the end, and leaves the initial arrays unchanged.
Returning a new array instance, as opposed to recycling the existing array instance is an important distinction, that makes it very useful for state objects in React applications, to get changed data to re-render.
I posted this in TC39's communication hub, and was able to learn a bit more about the history behind this:
push, pop, shift, unshift were originally added to JS1.2 (Netscape 4) in 1997.
There were modeled after the similarly named functions in Perl.
JS1.2 push followed the Perl 4 convention of returning the last item pushed.
In JS1.3 (Netscape 4.06 summer 1998) changed push to follow the Perl 5 conventions of returning the new length of the array.
see original jsarray.c source
/*
* If JS1.2, follow Perl4 by returning the last thing pushed. Otherwise,
* return the new array length.
*/
I cannot explain why they chose to return the new length, but in response to your suggestions:
Returning the newly appended item:
Given that JavaScript uses C-style assignment which emits the assigned value (as opposed to Basic-style assignment which does not) you can still have that behavior:
var addedItem;
myArray.push( addedItem = someExpression() );
(though I recognise this does mean you can't have it as part of an r-value in a declaration+assignment combination)
Returning the mutated array itself:
That would be in the style of "fluent" APIs which gained popularity significantly after ECMAScript 3 was completed and it would not be keeping in the style of other library features in ECMAScript, and again, it isn't that much extra legwork to enable the scenarios you're after by creating your own push method:
Array.prototype.push2 = function(x) {
this.push(x);
return this;
};
myArray.push2( foo ).push2( bar ).push2( baz );
or:
Array.prototype.push3 = function(x) {
this.push(x);
return x;
};
var foo = myArray.push3( computeFoo() );
I was curious since you asked. I made a sample array and inspected it in Chrome.
var arr = [];
arr.push(1);
arr.push(2);
arr.push(3);
console.log(arr);
Since I already have reference to the array as well as every object I push into it, there's only one other property that could be useful... length. By returning this one additional value of the Array data structure, I now have access to all the relevant information. It seems like the best design choice. That, or return nothing at all if you want to argue for the sake of saving 1 single machine instruction.
Why was it done like this, and is there a historical record of how these decisions came to be made?
No clue - I'm not certain a record of rationale along these lines exists. It would be up to the implementer and is likely commented in any given code base implementing the ECMA script standards.
I don't know "Why was it done like this, and is there a historical record of how these decisions came to be made?".
But I also think it's not clear and not intuitive that push() returns the length of array like below:
let arr = ["a", "b"];
let test = arr.push("c");
console.log(test); // 3
Then, if you want to use clear and intuitive method instead of push(), you can use concat() which returns the array with its values like below:
let arr = ["a", "b"];
let test = arr.concat("c");
console.log(test); // ["a", "b", "c"]
The question is partially answered in the document you mention (Ecma 262 3rd edition), there are methods that mutate the array and methods that don't. The methods that mutate the array will return the length of the mutated array. For adding elements that would be push, splice and unshift (Depending on the position you want the new element in).
If you want to get the new mutated array you can use concat. Concat will input any number of arrays you want added to the original array and add all the elements into a new array. i.e:
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3=['g','h'];
const array4 = array1.concat(array2,array3);
The new array created will have all the elements and the other three won't be changed. There are other (Many) ways to add the elements to an array both mutative and not mutative. So there is your answer, it returns the length because it is changing it, it doesn't need to return the full array.
Ever since its introduction in ECMA-262, 3rd Edition, the Array.prototype.push method's return value is a Number:
15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )
The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.
What were the design decisions behind returning the array's new length, as opposed to returning something potentially more useful, like:
A reference to the newly appended item/s
The mutated array itself
Why was it done like this, and is there a historical record of how these decisions came to be made?
I understand the expectation for array.push() to return the mutated array instead of its new length. And the desire to use this syntax for chaining reasons.
However, there is a built in way to do this: array.concat().
Note that concat expects to be given an array, not an item. So, remember to wrap the item(s) you want to add in [], if they are not already in an array.
newArray = oldArray.concat([newItem]);
Array chaining can be accomplished by using .concat(), as it returns an array,
but not by .push(), as it returns an integer (the new length of the array).
Here is a common pattern used in React for changing the state variable, based on its prior value:
// the property value we are changing
selectedBook.shelf = newShelf;
this.setState((prevState) => (
{books: prevState.books
.filter((book) => (book.id !== selectedBook.id))
.concat(selectedBook)
}
));
state object has a books property, that holds an array of book.
book is an object with id, and shelf properties (among others).
setState() takes in an object that holds the new value to be assigned to state
selectedBook is already in the books array, but its property shelf needs to be changed.
We can only give setState a top level object, however.
We cannot tell it to go find the book, and look for a property on that book, and give it this new value.
So we take the books array as it were.
filter to remove the old copy of selectedBook.
Then concat to add selectedBook back in, after updating its shelf property.
Great use case for wanting to chain push.
However, the correct way to do this is actually with concat.
Summary:
array.push() returns a number (mutated array's new length).
array.concat([]) returns a new array.
Technically, it returns a new array with the modified element added to the end, and leaves the initial arrays unchanged.
Returning a new array instance, as opposed to recycling the existing array instance is an important distinction, that makes it very useful for state objects in React applications, to get changed data to re-render.
I posted this in TC39's communication hub, and was able to learn a bit more about the history behind this:
push, pop, shift, unshift were originally added to JS1.2 (Netscape 4) in 1997.
There were modeled after the similarly named functions in Perl.
JS1.2 push followed the Perl 4 convention of returning the last item pushed.
In JS1.3 (Netscape 4.06 summer 1998) changed push to follow the Perl 5 conventions of returning the new length of the array.
see original jsarray.c source
/*
* If JS1.2, follow Perl4 by returning the last thing pushed. Otherwise,
* return the new array length.
*/
I cannot explain why they chose to return the new length, but in response to your suggestions:
Returning the newly appended item:
Given that JavaScript uses C-style assignment which emits the assigned value (as opposed to Basic-style assignment which does not) you can still have that behavior:
var addedItem;
myArray.push( addedItem = someExpression() );
(though I recognise this does mean you can't have it as part of an r-value in a declaration+assignment combination)
Returning the mutated array itself:
That would be in the style of "fluent" APIs which gained popularity significantly after ECMAScript 3 was completed and it would not be keeping in the style of other library features in ECMAScript, and again, it isn't that much extra legwork to enable the scenarios you're after by creating your own push method:
Array.prototype.push2 = function(x) {
this.push(x);
return this;
};
myArray.push2( foo ).push2( bar ).push2( baz );
or:
Array.prototype.push3 = function(x) {
this.push(x);
return x;
};
var foo = myArray.push3( computeFoo() );
I was curious since you asked. I made a sample array and inspected it in Chrome.
var arr = [];
arr.push(1);
arr.push(2);
arr.push(3);
console.log(arr);
Since I already have reference to the array as well as every object I push into it, there's only one other property that could be useful... length. By returning this one additional value of the Array data structure, I now have access to all the relevant information. It seems like the best design choice. That, or return nothing at all if you want to argue for the sake of saving 1 single machine instruction.
Why was it done like this, and is there a historical record of how these decisions came to be made?
No clue - I'm not certain a record of rationale along these lines exists. It would be up to the implementer and is likely commented in any given code base implementing the ECMA script standards.
I don't know "Why was it done like this, and is there a historical record of how these decisions came to be made?".
But I also think it's not clear and not intuitive that push() returns the length of array like below:
let arr = ["a", "b"];
let test = arr.push("c");
console.log(test); // 3
Then, if you want to use clear and intuitive method instead of push(), you can use concat() which returns the array with its values like below:
let arr = ["a", "b"];
let test = arr.concat("c");
console.log(test); // ["a", "b", "c"]
The question is partially answered in the document you mention (Ecma 262 3rd edition), there are methods that mutate the array and methods that don't. The methods that mutate the array will return the length of the mutated array. For adding elements that would be push, splice and unshift (Depending on the position you want the new element in).
If you want to get the new mutated array you can use concat. Concat will input any number of arrays you want added to the original array and add all the elements into a new array. i.e:
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3=['g','h'];
const array4 = array1.concat(array2,array3);
The new array created will have all the elements and the other three won't be changed. There are other (Many) ways to add the elements to an array both mutative and not mutative. So there is your answer, it returns the length because it is changing it, it doesn't need to return the full array.
I have a complex variable as an object including 2 times the same object.
if I change a value of the first object part, this will assume to change the value of the second part. is there an explanation? why are the tow keys still connected?!
here is a simple example of my code:
arr1={'a':[],'b':{'b1':'','b2':''}};
arr2={'p1':{...arr1},'p2':{...arr1}};
arr2['p1']['a']=[1,2,3];
console.log(arr2['p2']['a']); // works => []
arr2['p1']['b']['b1']='blabla';
console.log(arr2['p2']['b']['b1']); // doesn't work => 'blabla'
I don't want to write 'B={'b1':{'a':''},'b2':{'a':''}}' because A is a very big object in a separated .js file
A={'a':''};
B={'b1':A,'b2':A};
B['b1']['a']='blabla';
The main reason is that you try to change the object element via its index a instead of changing the object (reference). Step by step,
you ask to find B['bi'], which is A object
and then you ask to set A['a'] to blabla
B['b1'] and B['b2'] point to A
you get "both" changed since it's same instance of A
But if you do B['b1'] = { 'a': 'blabla' }, then you will not get the same result. Because now you are adding a new instance without any connection to A.
- use spread operator will save you from a problem like this
The fundamental idea of the spread operator is to create a new plain object using the own properties of an existing object. So {...obj} creates a new object with the same properties and values as obj
you can do that easily like
A={'a':''};
B={'b1':{...A},'b2':{...A}};
B['b1']['a']='blabla';
console.log(B['b2']); // => '' not 'blabla'
I solved the problem with:
arr2={'p1':JSON.parse(JSON.stringify(arr1)),'p2':JSON.parse(JSON.stringify(arr1))};
In my Vue.js app, I have an array value that should only be updated when a user completes a specific "refresh" action. However, as soon as I assign a new value to that array value, the array value becomes reactive and changes instantly as the data in the assigned value changes. The array value should remain un-reactive.
For example, I have a method, refresh(), which when triggered is meant to update displayedData, which should not be reactive, with currentData, which should be reactive. displayedData should only update when refresh is called.
methods: {
refresh: function() {
this.displayedData = this.currentData;
}
}
To make a value not reactive without making it static, you can make a "deep copy" of it using stucturedClone(), which is becoming widely supported as of 2022:
this.displayedData = structuredClone(this.currentData);
Another widely used method is to use JSON to encode and then decode it:
this.displayedData = JSON.parse(JSON.stringify(this.currentData));
Both of these methods assign the current state of one value to another value, and no changes to the first value will change the second value until this code is triggered again.
The reason this is necessary isn't because of Vue.js specifically, but because of JavaScript in general. In JavaScript, arrays and objects are "passed by reference" rather than "passed by value".
An approach (WITHOUT disabling reactivity) is to use a different array for your temporary data and move stuff over in to the good array when the user presses refresh.
You could copy the values in the first array into the temp array like this.temp = this.permanent.slice() in the created or mounted life-cycle function.
Slice would make a shallow copy of the array. If you need to also clone the items in the array, then maybe use some deep copy library or the JSON.parse(JSON.stringify(...)) method.
If you DON'T add the currentData to the data() method then it will not be reactive.
export default {
currentData: [],
data() {
return {
}
},
methods: {
refresh: function() {
this.displayedData = this.currentData;
}
}
}
You can then still reference currentData in the section using {{ $options.currentData }}
You can use destructuring assigment:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment?retiredLocale=it
this.displayedData = {...this.currentData}
You will have a new object made with the "copied" data from the first
I have an array or objects named "properties"
This properties array has objects in the following format:
{
"some-unique-key-A": {
name:"someName1",
value: {
"some-unique-key-B": {
name:"someName11",
value : "someValue2"
}
}
}
}
Here we don't know what is the nesting-level of that key value pairs. We just know one thing, data is to be updated for key : "some-unique-key-X"
In this case how can we update the x-level nested data immutably?
Use a flat one level state of props ! Immutable is not easy to recurse, normalize your state with one level, easy to connect ;)
This is how I solved this
I created a recursive function which will accept a 'deep copy' (not shallow copy) of some object and the unique key in which value needs to be updated. It updation is done for the given key, it returns the updated object to be stored as the value of previous recursive object's value which was waiting for the result. In the end it just replaces the original state's object with the new one. I don't know if it mutates the state or not (perhaps it do mutates), but it works.