For some reason I can't do this
this.state.something.map(obj => obj.id)
because it caused extra rerendering. How to get something from state, without assign it to a new variable?
I used to do this ugly hack
something_state_holder = this.state.something
something_state_holder.map(obj => obj.id)
but is there any better / more elegant way?
As #mayank-shukla said, what you are doing here is perfectly valid. The map() method iterates through an array and returns and array, but without mutating the iterated one. Here's what the MDN documentation says:
The map() method creates a new array with the results of calling a provided function on every element in this array.
map does not mutate the array on which it is called (although callback, if invoked, may do so).
However, should you want to make a shallow copy on an array, you could use slice:
something_state_holder = this.state.something.slice();
var resulting_array = something_state_holder.map(obj => obj.id);
This iterates through the copy, and not the state. What you did is not creating an array, you are simply assigning the reference to the same array to a new variable. So mutating that will mutate the state.
Related
I am working on an Angular 9, RxJS 6 app and have a question regarding the different outcomes of piping subject values and doing unit conversion in that pipe.
Please have a look at this stackblitz. There, inside the backend.service.ts file, an observable is created that does some "unit conversion" and returns everything that is emmitted to the _commodities Subject. If you look at the convertCommodityUnits function, please notice that I commented out the working example and instead have the way I solved it initially.
My question: When you use the unsubscribe buttons on the screen and subscribe again, when using the "conversion solution" that just overrides the object without making a copy, the values in the HTML are converted multiple times, so the pipe does not use the original data that the subject provides. If you use the other code, so creating a clone of the commodity object inside convertCommodityUnits, it works like expected.
Now, I don't understand why the two ways of converting the data behave so differently. I get that one manipulates the data directly, because js does Call by sharing and one returns a new object. But the object that is passed to the convertCommodityUnits function is created by the array.prototype.map function, so it should not overwrite anything, right? I expect that RxJS uses the original, last data that was emitted to the subject to pass into the pipe/map operators, but that does not seem to be the case in the example, right?
How/Why are the values converted multiple times here?
This is more or less a follow-up question on this: Angular/RxJS update piped subject manually (even if no data changed), "unit conversion in rxjs pipe", so it's the same setup.
When you're using map you got a new reference for the array. But you don't get new objects in the newly generated array (shallow copy of the array), so you're mutating the data inside the element.
In the destructuring solution, because you have only primitive types in each object in the array, you kind of generate completely brand new elements to your array each time the conversion method is called (this is important: not only a new array but also new elements in the array => you have performed a deep copy of the array). So you don't accumulate successively the values in each subscription.
It doesn't mean that the 1-level destructuring solution like you used in the provided stackblitz demo will work in all cases. I've seen this mistake being made a lot out there, particularly in redux pattern frameworks that need you to not mutate the stored data, like ngrx, ngxs etc. If you had complex objects in your array, the 1-level destructuring would've kept untouched all the embedded objects in each element of the array. I think it's easier to describe this behavior with examples:
const obj1 = {a: 1};
const array = [{b: 2, obj: obj1}];
// after every newArray assignment in the below code,
// console.log(newArray === array) prints false to the console
let newArray = [...array];
console.log(array[0] === newArray[0]); // true
newArray = array.map(item => item);
console.log(array[0] === newArray[0]); // true
newArray = array.map(item => ({...item}));
console.log(array[0] === newArray[0]); // false
console.log(array[0].obj === newArray[0].obj); // true
newArray = array.map(item => ({
...item,
obj: {...item.obj}
}));
console.log(array[0] === newArray[0]); // false
console.log(array[0].obj === newArray[0].obj); // false
Is there a specific reason why is better to use .map than for loops in React?
I'm working on a project where all arrays are being iterated with for loops but I'm convinced that is better and good practice to use .map because it creates a copy of the array, that to my understanding is better practice but I can't find a specific reason.
Is there a specific reason why is better to use .map than for loops in React?
If you're just iterating, map is the wrong tool. map is for mapping arrays: producing a new array based on the values from the previous array. Someone somewhere is teaching map as an iteration tool, unfortunately, doing their students a disservice. (I wish I knew who it was so I could have a word.) Never do this:
// Don't do this
myArray.map(entry => {
// ...do something with `entry`...
});
For iteration, it's a choice between a for loop, a for-of loop, and the forEach method. (Well, and a few other things; see my answer here for a thorough rundown.)
For instance, using forEach
myArray.forEach(entry => {
// ...do something with `entry`...
});
vs. using for-of:
for (const entry of myArray) {
// ...do something with `entry`...
}
(Those aren't quite equivalent. The former has to be an array. The latter can be any iterable object.)
The reason you may see map a lot in React is that you're frequently mapping things in React, in at least two ways:
Mapping from raw data to JSX elements, like this:
return (
<div>
{myData.map(({id, name}) => <div key={id}>{name}</div>)}
</div>
);
Since that's a mapping operation, with the array created by map being used to provide the contents of the outer div, map is the right choice there.
Mapping from old state to new state, like this:
const [myArray, setMyArray] = useState([]);
// ...
setMyArray(myArray.map(obj => {...obj, update: "updated value"}));
Since that again is a mapping operation, creating a new array to set as the myArray state member, map is the right choice.
...but I'm convinced that is better and good practice to use .map because it creates a copy of the array...
If you want a copy/updated version of the array, yes, map is a good choice. It's more concise than the equivalent for loop (or even for-of):
const newArray = oldArray.map(entry => /*...update...*/);
vs.
// Probably not best practice unless you have conditional logic
// in the loop body that may or may not `push` (or similar)
const newArray = [];
for (const entry of oldArray) {
newArray.push(/*...update...*/);
}
.map() maps each array value to a new value, and returns a brand new array.
In React.js context, .map() can be used to map each array item to a piece of JSX fragment.
for loop also iterates over an array, just like .map(). The major difference is that you can specify custom computation with for loop. Whereas .map() is specifically designed for mapping
I've a react application, and i've found a bug where i'm filtering an array for searching the current item the user has selected and then i'm going to do stuff with it...but, didn't know that filter function return a reference to the array's item, so, every change i made to the selected item i'm doing the same to the prop array.
const pippo = maledettoArray.filter(item => item.id === idInfame)[0];
How can i filter the prop array to get the specific item without changing it?
You can use find method instead of filter which returns first match and exits the loop.
const pippo = maledettoArray.find(item => item.id === idInfame)
To create shallow copy of the object you can use Object.assign or spread syntax.
const clone = {...pipo}
If you want to create deep copy of the nested object then you could use Lodash _.cloneDeep(value) method.
First of all, I would recommend you use the find function instead of filter to avoid the empty array return and undefined at [0]
Secondly, yes, the object reference would be returned. To avoid this, you can use Object.assign({}, originalObject) or using the spread syntax {...originalObject} . A potential problem would be with nested objects which could still be a problem.
Probably this article can help you in such case https://medium.com/#Farzad_YZ/3-ways-to-clone-objects-in-javascript-f752d148054d
Currently I've a react function that removes from a Array called rents the current rent perfect. The issue is that I need to update the rent row value called status and set property from 1 to 4 the code below works. I don't seem to get how to get the Index of rent to be able to update it.
removeItem (itemIndex) {
this.state.rents.splice(itemIndex, 1) // removes the element
this.setState({rents: this.state.rents}) // sets again the array without the value to the rent prop
console.log(itemIndex) // itemIndex
}
currently I'm adding this to the code to debug but get this error
console.log(this.state.rents[itemIndex].unique_key)
Stack Trace
TypeError: Cannot read property 'unique_key' of undefined
I need to be able to update the rent property value called status from 1 to 4 and setState again
To elaborate the comments, starting first with the most important:
Like #devserkan said, you should never mutate your state (and props), otherwise you start to see some really weird hard-to-make-sense bugs. When manipulating state, always create a copy of it. You can read more here.
Now for your question:
this.setState is asynchronous, so to get your state's updated value you should use a callback function
const rents = [...this.state.rents]; // create a copy
rents.splice(itemIndex, 1);
this.setState({ rents }, () => {
console.log(this.state.rents); // this will be up-to-date
});
console.log(this.state.rents); // this won't
Personally, I like using the filter method to remove items from the state and want to give an alternative solution. As we tried to explain in the comments and #Thiago Loddi's answer, you shouldn't mutate your state like this.
For arrays, use methods like map, filter, slice, concat to create new ones according to the situation. You can also use spread syntax to shallow copy your array. Then set your state using this new one. For objects, you can use Object.assign or spread syntax again to create new ones.
A warning, spread syntax and Object.assign creates shallow copies. If you mutate a nested property of this newly created object, you will mutate the original one. Just keep in mind, for this situation you need a deep copy or you should change the object again without mutating it somehow.
Here is the alternative solution with filter.
removeItem = itemIndex => {
const newRents = this.state.rents.filter((_, index) => index !== itemIndex);
this.setState({ rents: newRents });
};
If you want to log this new state, you can use a callback to setState but personally, I like to log the state in the render method. So here is one more alternative :)
render() {
console.log( this.state.rents );
...
}
I am reading the document of Reactjs about not mutating data.
I do not understand the difference between 2 pieces of code in document's example:
handleClick() {
// This section is bad style and causes a bug
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}
and:
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
Why the second code does not mutate the data?
Simply put, concatreturns a new copy of the array, hence it doesn't mutate the previous array.
In the first example you have an array and are adding a new item, by using push to this array.
In the second example you are creating a copy of the array, by using concat and adding the item, which means you do not mutate the original array. concat returns a new array.
In the second example you are not mutating the original array. Not mutating state is an important principle in React and Redux.
There is a good answer here explaining the reasons for avoiding mutation.