ReactJS Spread Operator - javascript

I am using React.
I have an array of objects in my state.
this.state = {
team: [{
name:'Bob',
number:23
},
{
name:'Jim',
number:43
}]
}
When I try to make a copy of the array to change a object's property, I don't get the results I want.
I have tried:
let tempTeam = [...this.state.team]
Any change to tempTeam also mutates this.state.team
AND
let tempTeam = this.state.team.map(player => return {...player})
This just throws an error, it doesn't like {...player}
How can I get the array of objects without it referencing this.state.team?

You have a syntax error in the posted code. If you change it to:
let tempTeam = this.state.team.map(player => ({...player}));
Or:
let tempTeam = this.state.team.map(player => {
return { ...player };
});
You will get a new array with the same object values without the references.

Object.assign and spread syntax create shallow copies. If you mutate a nested property in the copied one you also mutate original object.
Most of the time we use map, filter, slice to get a new array by using the original one. But, even using those methods, we should not mutate the properties directly, we should return again new ones using Object.assign or spread syntax again.
As explained you have a syntax error in your code, if you fix it you can get the new array. But, most of the time you will do operations like these:
const state = {
team: [{
name:'Bob',
number:23
},
{
name:'Jim',
number:43
}]
};
const changeAll = state.team.map( player => ({
...player, number: player.number + 1,
}));
// or
const playerTheOne = state.team.filter(player => player.number === 23);
const notPlayerTheOne = state.team.filter(player => player.number !== 23);
// or
const changeJustOne = state.team.map( player => {
if ( player.number === 23 ) {
return { ...player, name: "Joe" };
}
return player;
});
console.log( state.team );
console.log( changeAll );
console.log( playerTheOne );
console.log( notPlayerTheOne );
console.log( changeJustOne );
As you can see, you don't create a new array then mutate it, you are mutating it while you are creating it.

Using Array.slice() creates a clone, so this should should work:
let tempTeam = this.state.team.slice();

Related

JavaScript - Creating Object with specific format out of 2 Arrays

I have 2 Arrays.
The Array "people" just holds basic information of some people.
The Array "subscriptions" has a lot of different subscriptions to games.
I want to have have an Array, where I can sort of have an Overview of what game subscriptions each person has. This is not real code I use, I am just trying to get used to JavaScript.
an example Element of an Array called people:
{"number":4251,"name":"Alex","surname":"Scott"}
an example Element of an Array called subscriptions:
{"number":4329,game:"Tetris"}
I want to make a new new Array with the following format:
person: (people[i]), subscriptions: [(subscriptions[j], subscriptions[j+k], ...)]
What I tried:
const array3 = people.map(x => {"person": x , "subscriptions": subscriptions.filter(y => y.number === x.number)});
It get this Error:
SyntaxError: Unexpected token :
How can I insert multiple key value pairs in in these Objects?
This happens because your argument in the map is interperting the { as opening bracket of the method body.
const arr = [{some: 1, object: 2}, {some:3, object:4}]
arr.map((o) => {original: o, cool: 'yes'})
To resolve this you have to explicitly return the object or add additional parenthesis to let the interperter know that this is not the method body:
const arr = [{some: 1, object: 2}, {some:3, object:4}]
const mapped = arr.map((o) => {return {original: o, cool: 'yes'}})
console.log(mapped)
const arr = [{some: 1, object: 2}, {some:3, object:4}]
const mapped = arr.map((o) => ({original: o, cool: 'yes'}))
console.log(mapped)
I hope this helps!
In your original code there is a syntax error, when it comes to the mapping part. Either you go with the long version of this command or, since you directly want to return the element, you can use the short version:
const people = [{"number":4251,"name":"Alex","surname":"Scott"}, {"number": 4329, "name":"Mr", "surname": "Tetri"}]
const subscriptions = [{"number":4329,game:"Tetris"}, {"number":4329, game:"Solitaire"}, {number: 4251, game: "Tetris"}]
// using an anonymous function including full body
const arrayLongVersion = people.map(x => { return {"person": x , "subscriptions": subscriptions.filter(y => y.number === x.number)} });
// using an anonymous function with direct return of elements
const arrayShortVersion = people.map(x => ({person: x , subscriptions: subscriptions.filter(y => y.number === x.number)}))
Here you go
const people = [
{"number":1,"name":"Alex","surname":"Scott"},
{"number":2,"name":"John","surname":"Anderson"},
{"number":3,"name":"Jacob","surname":"Sanderson"},
{"number":4,"name":"Lionel","surname":"Messi"},
{"number":5,"name":"Cristiano","surname":"Ronaldo"}
];
const subscriptions = [
{"number":1,game:"Tetris"},
{"number":2,game:"Super Mario"},
{"number":3,game:"Fortnite"},
{"number":4,game:"FIFA"},
{"number":5,game:"Zelda"}
];
const peopleSubscriptions = people.map(person => {
return {
...person,
subscriptions: subscriptions.filter(sub => sub.number === person.number)
}
});
console.log(peopleSubscriptions);

React setState Array Hook doesn't re-render component [duplicate]

I want to update value of one object only but updating value of one Object, Updates the value for all objects.
let default = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(default)
}
updateName (event) {
let index = event.target.id,
values = this.state.values;
values[index].name = event.target.value;
this.setState ({
values: values
});
}
There are four significant problems in that code.
You're using the same object for all entries in your array. If you want to have different objects, you have to create multiple copies of the default.
You're calling setState incorrectly. Any time you're setting state based on existing state (and you're setting values based, indirectly, on this.state.values), you must use the function callback version of setState. More: State Updates May Be Asynchronous
You can't directly modify the object held in this.state.values; instead, you must make a copy of the object and modify that. More: Do Not Modify State Directly
default is a keyword, you can't use it as an identifier. Let's use defaultValue instead.
Here's one way you can address all four (see comments):
// #4 - `default` is a keyword
let defaultValue = {
name: '',
age: ''
};
this.state = {
// #1 - copy default, don't use it directly
values: [
Object.assign({}, defaultValue),
Object.assign({}, defaultValue),
] // <=== Side note - no ; here!
};
// ....
updateName(event) {
// Grab the name for later use
const name = event.target.value;
// Grab the index -- I __don't__ recommend using indexed updates like this;
// instead, use an object property you can search for in the array in case
// the order changes (but I haven't done that in this code).
const index = event.target.id;
// #2 - state updates working from current state MUST use
// the function callback version of setState
this.setState(prevState => {
// #3 - don't modify state directly - copy the array...
const values = prevState.values.slice();
// ...and the object, doing the update; again, I wouldn't use an index from
// the `id` property here, I'd find it in the `values` array wherever it
// is _now_ instead (it may have moved).
values[index] = {...values[index], name};
return {values};
});
}
Note that this line in the above:
values[index] = {...values[index], name};
...uses property spread syntax added in ES2018 (and shorthand property syntax, just name instead of name: name).
I would use the Array.prototype.map function with combination of the object spread syntax (stage 4):
Note that i changed the name of the default object to obj.
default is a reserved key word in javascript
let obj = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(obj)
}
updateName(event){
const {id, value} = event.target;
this.setState(prev => {
const {values} = prev;
const nextState = values.map((o,idx) => {
if(idx !== id)
return o; // not our object, return as is
return{
...o,
name: value;
}
});
return{
values: nextState
}
});
}
There is an easy and safe way to achieve that through the following:
this.setState({
values: [ newObject, ...this.state.values],
});
this will create an instance of the state and change the value of an existing object with new object.

Ensuring immutability of the original when setting and deleting from a map object

I've been studying how to accomplish immutability/functional programming in JavaScript and I am having difficulties with the following code. The object returned from the generateExample function contains methods to add and remove keys/values from a map, by iterating over an array of objects.
Scenario 1 simply uses the map methods set and delete methods and mutates the map. Scenario 2 creates a new map prior to setting and deleting keys/values.
I could clone the map for each new set, in each foreach loop, but I'd imagine that would be too computationally expensive.
I could also use Immutable.js (Map) and have a new data structure returned for each set and delete. I don't really want to do this.
The object returned from generateExample in this example would be the only object that that could set and delete to the map, so perhaps worrying about immutability in this is an example is not important?
What I am trying to understand is what is the best way to maintain immutability of the original map when setting and deleting keys/values and if it's even relevant?
const sampleList = [
{
id: 'dog',
value: 'Dog',
},
{
id: 'cat',
value: 'Cat',
},
]
SCENARIO 1
const example = function generateExample() {
const container = new Map()
return {
add(list) {
list
.forEach((x) => {
container.set(x.id, x)
})
},
remove(list) {
list
.forEach((x) => {
container.delete(x.id)
})
},
read(id) {
return container.get(id)
},
}
}
SCENARIO 2
const example = function generateExample() {
let container = new Map()
return {
add(list) {
container = new Map(container)
list
.forEach((x) => {
container.set(x.id, x)
})
},
remove(list) {
container = new Map(container)
list
.forEach((x) => {
container.delete(x.id)
})
},
read(id) {
return container.get(id)
},
}
}
const test = example()
test.add(sampleList)
console.log(test.read('dog'))
In both cases you have mutations. In the first scenario the mutation is over Map object, in the second scenario the mutation is over container value itself then again on Map object.I'll suggest not to have this layer but just treating the Map objects as they are, but if the case must be like this, I'll prefer to do something like this
const example = function generateExample(defaultList) {
let container = new Map(defaultList)
return {
add(list) {
container = new Map(container.concat(list));
},
remove(list) {
container = new Map(container.filter(item => !list.includes(item)))
},
read(id) {
return container.get(id)
},
}
}

Filter a javascript tree without mutating the original array

I'm working on a react project where I need to filter an array of objects without mutating the original array
const array = [{
name: 'bar',
children: [{
name: 'foo',
children: [{
name: 'baz123',
}, {
name: 'baz',
}]
}]
}, {
name: 'shallowKey'
}, {
name: 'abc'
}];
For example, I want to only filter the concerned object and its children.
This is the jsfiddle
function filterData(data) {
var r = data.filter(function(o) {
if (o.children) o.children = filterData(o.children);
return o.name.length === 3;
})
return r;
}
I tried that function from a stackoverflow question, but is there a way to use that same functionality without mutating the data. Thanks
If you don't have any prototypes or functions involved within the objects a simple way to copy is to stringify original and parse it back to object
var r= JSON.parse(JSON.stringify(data)).filter(...
Array .filter() already creates a new array, you just need to fix the part where the o.children is mutated. To do that you could use .map() and simply copy all fields using Object.assign() or object spread and just assign children as the result passed through the same filter function:
function filterData(data) {
return data
.filter(obj => obj.name.length === 3) // filter array first
.map(obj => ({ // then re-map to new objects
...obj, // copy shallow fields
children: obj.children && filterData(obj.children) // filter children
}));
}
You can create a copy of your original array using a spread operator or Object.assign() function.
const arrayCopy= [...array] //spread operator
const arrayCopy = Object.assign({}, array);
Otherwise as Aaron suggested, using filter(), map(), reduce() function always returns a new array without mutating your original array.

Converting flat array to Id and Name object array using Lodash

let states = ["Georgia","California","FL","TX","MA","NJ"];
How do I convert the states array into Id and Name array collection using lodash.
Is there a way to convert the array in below format ( image shown below):
You don't really need lodash to do that.
let states = ["Georgia","California","FL","TX","MA","NJ"];
let result = states.map((item) => {
return {
id: item,
name: item}})
console.log(result)
You do pretty much the same with lodash
import _ from 'lodash';
result = _.map(states, (item) => {
return {
id: item,
name: item}})
let states = ["Georgia","California","FL","TX","MA","NJ"];
const newObj = [];
_.each(states, state => newObj.push({ id: state, name: state }));
console.log(newObj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
_.each performs a function on each item of an Array. With that you can create a new object for each state and push that into a new Array. Note: this could also be accomplished with JavaScript's built in .map.
----- UPDATE -----
Why did I make this complicated many years ago?
const states = ["Georgia","California","FL","TX","MA","NJ"];
const newObj = states.map(state => ({ id: state, name: state }));
console.log(newObj);
No need to use lodash, just map through the array and return a new object for each item in the array.

Categories

Resources