React & Javascript optimization - arrays, spread, splice - javascript

I have a little problem understanding something. So I need your help to do an example for me. Please kindly write this two functions with a better solution.
the setTempRecipe is a coming from useState hook in my react functional component.
const addCustomizationOption = (co) => {
const tmpR = Object.assign({}, tempRecipe);
tmpR.customizationOptions.push(co);
setTempRecipe(tmpR);
};
and the second one is:
const removeCustomizationOption = (co) => {
const tmpR = Object.assign({}, tempRecipe);
const g = tmpR.customizationOptions.findIndex(item => item.id === co.id);
tmpR.customizationOptions.splice(g, 1);
setTempRecipe(tmpR);
};

const addCustomizationOption = (co) => {
setTempRecipe({
...tempRecipe,
customizationOptions:[...tempRecipe.customizationOptions,co]
});
};
const removeCustomizationOption = (co) => {
setTempRecipe({
...tempRecipe,
customizationOptions:tempRecipe.customizationOptions.filter(i => i.id !== co.id)
});
};

You have the right idea, you shouldn't mutate state variables, instead take a deep copy and apply new values. What I would go for is:
const addCustomizationOption = (co) => {
setTempRecipe(tr => ({...tr, customizationOptions: [...tr.customizationOptions,co]}));
};
This spread operator will add co to the tempRecipe array and tr refers to the current state.
And, for the second one, you can filter the array:
const removeCustomizationOption = (co) => {
setTempRecipe(tr => ({...tr, customizationOptions: tr.customizationOptions.filter(recipe => recipe.id !== co.id))})
};
Since .filter() function returns a new array, you can directly filter out the id and set the new filtered array.

Related

Two similar react functions produce Inconsistent Results in rerendering

This function works properly and the component rerenders
handleRemove = (e) => {
//console.log(e);
const arrayCopy = this.state.scanlist.filter((row) => row.ref + row.lot !== e.ref + e.lot);
this.setState({ scanlist: arrayCopy });};
This function changes the state but the component does not rerender
handleAdd = (e) => {
//console.log(e);
const index = this.state.scanlist.findIndex((row) => row.ref === e.ref && row.lot === e.lot);
let scancopy = this.state.scanlist;
scancopy[index].qty = scancopy[index].qty + 1;
console.log(scancopy);
this.setState({ scanlist: scancopy });};
Does anyone see the issue? Mutation?
Using Array.prototype.filter to remove an element from an array is pretty standard, but in the second handler yes, you've a state object mutation.
handleAdd = (e) => {
const index = this.state.scanlist.findIndex((row) => row.ref === e.ref && row.lot === e.lot);
let scancopy = this.state.scanlist;
scancopy[index].qty = scancopy[index].qty + 1; // <-- state object mutation
this.setState({ scanlist: scancopy });
};
You should shallow copy the scanlist array and the element that is being updated. Array.prototype.map is a common method to shallow copy the state array, and spreading the object properties of the element that needs to be updated shallow copies it. Anytime you are updating any nested state objects you should shallow copy the parent object.
handleAdd = (e) => {
this.setState(prevState => ({
scanlist: prevState.scanlist.map(
(row) => row.ref === e.ref && row.lot === e.lot ? {
...row,
qty: row.qty + 1,
} : row)
}));
};
In the first approach filter method returns new list, whereas in the second approach it is just referencing to state array.
Try shallow copy in second approach,
let scancopy = [...this.state.scanlist]
Or
Simple deep copy approach,
let scancopy = JSON.parse(JSON.stringify(this.state.scanlist));

Insert element inside array

I have a function
checkName(output) {
output.filter((NewData) => {
return this.props.elements.filter((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name,
//there i need to add another element
// Need to add newData.number = OldData.number
}
return NewData
})
})
return output
}
and I call this function like:
const named = this.checkName(product.rows)
Now I need to add to my product's array that I passed to checkName the value "OldData.Number" to "newData.Number" that is not defined in product (so I need to create this field)
For example:
Product before the checkName function
product.rows = [NewData.name]
Product after the checkName function
product.rows = [NewData.name="value of OldData.name", NewData.number="value of OldData.number"]
How can I obtain this result?
There are 2 confusing things in your code:
You are using filter to execute an action in each member of the output array. However, filter should be used to... well, filter that array, meaning that is should not modify it, just return a sub-set of it. Instead, you might want to use forEach. However, taking into accound the next bullet, probably you want to use map.
You are modifying the array passed to the checkName function. This is confusing and can lead to hard-to-find bugs. Instead, make your function "pure", meaning that it should not mutate its inputs, instead just return the data you need from it.
I would suggest some implementation like this one:
checkName(output){
return output.map((NewData) => {
// find the old data item corresponding to the current NewData
const OldData = this.props.elements.find(x => x.key === NewData.key);
if (OldData) {
// If found, return a clone of the new data with the old data name
// This uses the spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
return {
...NewData, // Clone the NewData object
name: OldData.name, // set the value found in OldData.name in the "name" field of the cloned object
number: OldData.number, // You can do the same for each field for which you want to replace the value cloned from NewValue
};
} else {
// Otherwise, just return a clone of the NewData
return { ...NewData };
}
}
}
The usage would be like this:
const named = this.checkName(product.rows)
Be aware that the product.rows array won't be modified!
You can get keys and values of the old object.
const keys = Object.keys(oldObject);
const values = Object.values(oldObject);
// or
const [keys, values] = Object.entries(oldObject);
After, you will create a loop with all keys of oldObject, and insert in newObject like a array.
keys.forEach( (key, index) => newObject[key] = values[index]);
// or
for (const [key, value] of Object.entries(object1)) {
newObject[key] = value
}
Use map like this.
checkName(output){
return output.map(( NewData) =>{
this.props.elements.forEach((OldData) => {
if (NewData.key == OldData.key) {
NewData.name = OldData.name;
NewData.number = OldData.number;
}
})
return NewData;
})
// return output;
}

Set arguments dynamically with Promise.all().then()

The code below works for me
Promise.all([first, second, third]).then([first, second, third] => {
console.log(second);
});
I know that console.log(second) will give me the value with the key second.
My promises are dynamically set and now it looks like below:
let collection = [second, third];
Promise.all(collection).then((collection) => {
console.log(collection);
});
In this example I set two values in collection. In real life it can include more or less values.
When I use console.log(collection) it will output collection[0] and collection[1]. In this case I don't know what which value collection[1] is.
Question
How can I, like my first example, have something like named dynamically arguments like collection['second'] or similar?
As we want to access the value dynamically, set collection to an empty object first. Then, use the keys from collection to pass all its Promise-values to Promise.all. Then, map back the fulfilled values and then, we can access collection's value by some key.
let collection = {}
for (let i = 0; i < 3; i++) {
collection[`key${i}`] = Promise.resolve(i)
}
let collectionKeys = Object.keys(collection)
Promise.all(collectionKeys.map(key => collection[key]))
.then(values => {
let collectionFulfilled = collectionKeys.reduce((obj, key, i) => {
obj[key] = values[i]
return obj
}, {})
console.log(collectionFulfilled)
})
If you pass your promises embedded inside an object with a single key, you could use that for it's name, and then with a simple helper function reverse the values & keys from this.
With the new ES6 you can then just pass like -> [{one}, {two}, {three}] etc.
Below is an example with a helper function called namedPromiseAll.
function namedPromiseAll(named) {
const pcollection =
named.map(m => Object.values(m)[0]);
const ncollection =
named.map(m => Object.keys(m)[0]);
return Promise.all(pcollection).then((c) => {
return c.reduce((a,v,ix) => {
a[ncollection[ix]] = v;
return a;
}, {});
});
}
const second = Promise.resolve(2);
const third = Promise.resolve(3);
const collection = [{second}, {third}];
namedPromiseAll(collection).then(console.log);

Remove an object's key and value using a variable from function

Hey I'm trying to remove a key:value pair from state inside a Javascript Object.
It works when I hardcode the key name in the code, but when I try to use a variable from a function call, it does nothing.
Can somebody help me out?
Here's an object example:
toppingsSelected: {
"Onion":"true",
"Mushrooms":"true",
}
This works, hardcoded:
deleteTopping = toppingName => {
const { Onion, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns object without onion
};
This doesn't work:
deleteTopping = toppingName => {
const toppingName = "Onion"; // Variable gets passed in
const { toppingName, ...withoutOnion } = toppingsSelected;
console.log(withoutOnion); // Returns original object, no change made
};
So I'm basically trying to remove a key from React state but I'm pretty new to Javascript.
How can I make Javascript aware that toppingName is a key?
Another option is to add square brackets arround toppingName, and assign it to a variable. As #Bergi pointed out in the comments, this option does not mutate toppingsSelected
const toppingsSelected = {
"Onion":"true",
"Mushrooms":"true",
};
const toppingName = "Onion";
const {
[toppingName]: topping,
...withoutOnion
} = toppingsSelected;
console.log(JSON.stringify(withoutOnion));
To set the React state, you'd then do this
this.setState({ toppingsSelected: withoutOnion })
You can use delete e.g.
delete toppingsSelected[toppingName];
One way of doing this is using Array.prototype.filter()
const _obj = {
'Onion': true,
'notOnion': false
};
const newObj = Object.keys(_obj)
.filter(key => key !== 'Onion')
.reduce((acc, cur) => ({ ...acc, cur }), {})
console.log(newObj); // { notOnion: false }
This will return a new object without the 'Onion' property

How to get or set a JavaScript Map without repeating myself

I have a use case where I want to get an item from a Map. If that item doesn’t exist, I want to insert an initial value and save it for later. I also would like to use const variables so that I can’t accidentally rebind the local. I know I can write my own helper, but it seems to me that this pattern requires a lot of writing for a pattern which I expect to be quite common:
const item = myMap.has(key) ? myMap.get(key) : myMap.set(key, initialize()).get(key);
or to avoid the .get() immediately after the .set():
const item = myMap.has(key) ? myMap.get(key) : (() => {const value = initialize(); myMap.set(key, value); return value})();
Is there a simpler way to get an item from a Map into a const variable and, if it doesn’t yet exist, insert an initial value for the key first?
I know that a similar question exists, but in that question there is no requirement that the inserted item be stored in a local after insertion and it uses let rather than const.
EDIT: I’ve gone with the utility function route and created maputil.
We can reduce it ever so slightly, factoring out the .get(key) part:
const item = (myMap.has(key) ? myMap : myMap.set(key, initialize())).get(key);
...but I don't think we can go further without introducing a new function.
const initialize = _ => 42;
const myMap = new Map();
const key = "answer";
const item = (myMap.has(key) ? myMap : myMap.set(key, initialize())).get(key);
console.log(item);
console.log(Array.from(myMap.keys()));
So yeah, probably a candidate for a utility function. :-)
Standalone:
const getOrAdd = (map, key, initializer) => {
if (map.has(key)) {
return map.get(key);
}
const value = initializer();
map.set(key, value);
return value;
};
const initialize = _ => 42;
const myMap = new Map();
const key = "answer";
const item = getOrAdd(myMap, key, initialize);
console.log(item);
console.log(Array.from(myMap.keys()));
...or if you can extend builtins in your project (it's okay for some projects, not for others):
Object.defineProperty(Map.prototype, "getOrAdd", {
value: function(key, initializer) {
if (this.has(key)) {
return this.get(key);
}
const value = initializer();
this.set(key, value);
return value;
}
});
const initialize = _ => 42;
const myMap = new Map();
const key = "answer";
const item = myMap.getOrAdd(key, initialize);
console.log(item);
console.log(Array.from(myMap.keys()));

Categories

Resources