filter() in function is affecting another - javascript

In the below, I have a function that should be filtering accountView, but for some reason it's also filtering accountCompare. Not sure why this is happening. I thought I had assigned the two seperately so that accountCompare is always a constant.
getAccount() {
this.accounts.getAccount(this.accountId).subscribe(
response => {
this.accountView = this.apiHandler.responseHandler(response);
this.accountCompare = this.apiHandler.responseHandler(response);
console.log(this.accountCompare);
},
(err) => {
this.apiHandler.errorHandler(err);
}
);
}
//then in this function, I filter accountView, however it appears to also be affecting accountCompare as well.
userDelete(id) {
if (this.accountCompare.users.some(item => item.id === id)) {
this.accountForm.value.usersToDelete.push(id);
}
this.accountView.users = this.accountView.users.filter(user => user.id !== id);
/* this.accountForm.value.usersToAdd = this.accountForm.value.usersToAdd.filter(user => id !== id); */
console.log(this.accountCompare);
}

Non-primitive values are passed by reference. This means you are actually updating a reference, not a value.
A quick hack for you :
this.accountView = JSON.parse(JSON.stringify(this.apiHandler.responseHandler(response)));
this.accountCompare = JSON.parse(JSON.stringify(this.apiHandler.responseHandler(response)));

Related

why my lodash cloneDeepWith method only run once?

I have a very very very deep nested object state.
and i want to change all id properties at once with lodash cloneDeepWith methods.
i'm using cloneDeepWith and only works on first match.
if i dont return the modified object then it won't modifiy anything.
and if i return the value i think the function stops.
the function its working ok but the only problem is that only will run once.
const handleChangeIds = (value) => {
if (value === sections) {
const modifiedObject = cloneDeepWith(value, (sectionsValue) => {
if (sectionsValue && Object.hasOwn(sectionsValue, 'id')) {
const clonedObj = cloneDeep(sectionsValue);
clonedObj.id = generateObjectId();
return clonedObj;
// I Also Tried sectionsValue = clonedObj; its the same behavior
}
});
return modifiedObject;
}
};
const DuplicateSection = () => {
console.log('Original Store', form);
const store = cloneDeepWith(form, handleChangeIds);
console.log('Modified', store)
};
For those who want to achieve same thing like me.
I had a super deep nested object for form. and that form had a repeatable functionality.
and i needed to do two thing in generating another form.
generate new Id for every field Id.
clear the input Value.
I solved my problem like this
and it works perfectly for a super deep nested object.
import cloneDeepWith from 'lodash/cloneDeepWith';
const clearInputAndChangeId = (sections: FormSectionProps): FormSectionProps => {
return cloneDeepWith(sections, (value, propertyName, object) => {
if (propertyName === 'id') return generateObjectId();
if (propertyName === 'selected') return false;
if (propertyName === 'checked') return false;
if (propertyName === 'value') {
if (object.type === 'file') return [];
if (object.type === 'checkbox/rating') return 1;
return '';
}
});
};

React "magically" updates two states instead of one

I have two states defined like so:
const [productProperties, setProductProperties] = useState<
PropertyGroup[] | null
>(null);
const [originalProductProperties, setOriginalProductProperties] = useState<
PropertyGroup[] | null
>(null);
The first one is supposed to be updated through user input and the second one is used later for a comparison so that only the PropertyGroup's that have changed values will be submitted via API to be updated.
I have done this a thousand times before, but for some reason when I change the name value for a PropertyGroup and update the state for 'productProperties' like so:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i].name = {
...update[i].name,
[selectedLocale]: e.currentTarget.value,
};
setProductProperties([...update]);
}
}
The state of originalProductProperties also updates. Why? setOriginalProductProperties is never called here, I am also not mutating any state directly and I use the spread operator to be sure to create new references. I am lost.
Preface: It sounds like the two arrays are sharing the same objects. That's fine provided you handle updates correctly.
Although you're copying the array, you're modifying the object in the array directly. That's breaking the main rule of state: Do Not Modify State Directly
Instead, make a copy of the object as well:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update); // (No need to *re*copy the array here, you've already done it at the top of the function)
}
}
Or, since you have that i !== -1 check there, we could copy the array later so we don't copy it if we don't find the group matching itemId:
(e, itemId) => {
const i = productProperties.findIndex((group) => group.id === itemId);
if (i !== -1) {
const update = [...productProperties];
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update);
}
}
FWIW, in cases where you know there will be a match, map is good for this (but probably not in this case, since you seem to indicate the group may not be there):
(e, itemId) => {
const update = productProperties.map((group) => {
if (group.id === itemId) {
// It's the one we want, create the replacement
group = {
...group,
[selectedLocale]: e.currentTarget.value,
};
}
return group;
});
setProductProperties(update);
}
Or sometimes you see it written with a conditional operator:
(e, itemId) => {
const update = productProperties.map((group) =>
group.id === itemId
? { // It's the one we want, create a replacement
...group,
[selectedLocale]: e.currentTarget.value,
}
: group
);
setProductProperties(update);
}

Only push the object which is not in the array yet

This is my function:
const multiSelect = value => {
let tmpArr = [...selectedPeople];
if (tmpArr.length === 0) {
tmpArr.push(value);
} else {
tmpArr.map(item => {
if (item.id !== value.id) {
tmpArr.push(value);
} else {
return;
}
});
}
setSelectedPeople(tmpArr);
};
I want to check the array for the new value by comparing it with all items. If value === item item the loop function should return, but if the value is not in the array yet, it should push it.
This is a big problem for me but I assume it is a small problem for you guys.
Use Array.every() to check if the array doesn't contain an item with the same id:
const multiSelect = value => {
const tmpArr = [...selectedPeople];
if(tmpArr.every(item => item.id !== value.id)) {
tmpArr.push(value);
}
setSelectedPeople(tmpArr);
};
However, this means that you're duplicating the array needlessly, while causing a re-render, that won't do a thing. So check if the item is already a part of selectedPeople by using Array.some(), and if it does use return to exit the function early. If it's not continue with cloning, and updating the state:
const multiSelect = value => {
if(tmpArr.some(item => item.id === value.id)) {
return;
}
const tmpArr = [...selectedPeople];
tmpArr.push(value);
setSelectedPeople(tmpArr);
};
Use find to check if the item is already in the array. Also, there's no need to make a copy of the source array:
const multiSelect = value => {
if (!selectedPeople.find(item => item.id === value.id))
setSelectedPeople(selectedPeople.concat(value))
}
Another approach.
const
multiSelect = value => setSelectedPeople([
...selectedPeople,
...selectedPeople.some(({ id }) => id === value.id)
? []
: [value]
]);

Is there a simpler/cleaner way to write this?

I am using this code to sync with google storage. It works just want to know if there is a simpler and cleaner way to write this.
chrome.storage.sync.get(['titleCustom', 'textCustom', 'highCustom', 'quoteCustom', 'bgCustom'], (result) => {
if(result.titleCustom !== undefined){
document.documentElement.style.setProperty('--blue', result.titleCustom);
}
if(result.textCustom !== undefined){
document.documentElement.style.setProperty('--light-text', result.textCustom);
}
if(result.highCustom !== undefined){
document.documentElement.style.setProperty('--green', result.highCustom);
}
if(result.quoteCustom !== undefined){
document.documentElement.style.setProperty('--dark-text', result.quoteCustom);
}
if(result.bgCustom !== undefined){
document.documentElement.style.setProperty('--light-bg', result.bgCustom);
}
}
You could create a object mapping the key in result (e.g. titleCustom) to the respective color (e.g. --blue), then iterate through the object and so something like this:
// key:color are the variables of our outer for-each loop
if(result[key] !== undefined) {
document.documentElement.style.setProperty(color, result[key]);
}
This has the additional effect that you can use Object.keys(mapObject) as parameter in chrome.storage.sync.get instead of the hard-coded array.
Declare an object of key/values for the storage items where the key is the storage item and the value is the corresponding property you'd like to set for that key element.
let props = {
"titleCustom": "--blue",
"textCustom": "--light-text",
"highCustom" :"--green",
"quoteCustom": "--dark-text",
"bgCustom": "--light-bg"
}
];
let keys = Object.keys(props);
chrome.storage.sync.get(keys, (result) => {
keys.forEach((element) => {
if (element in result) {
document.documentElement.style.setProperty(props[element], result[element]);
}
});
});

How to give a function a return value when the return is inside an if statement

I had a function that was initially like this, and worked:
productGroupPages(): PDF.Page[] {
const descriptions = {};
this.areas.forEach(area => descriptions[area.id] = area.description);
return this.json.engagementAreas.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
Basically everything isnt wrapped in the for each.
I had to alter to the function as i wanted to go through a for each, to then use an if statement so i would only return values that actually contained a certain value, the result is that my return statement is too early, I cant move it to the end because of the for loop and im struggling to find out how I can get past this,
this new function:
productGroupPages(): PDF.Page[] {
const descriptions = {};
let pages = {};
console.log('this .json' + JSON.stringify(this.json))
this.json.engagementAreas.forEach(area => {
descriptions[area.id] = area.description
if (area.engagementTypes.length !== 0) {
return this.json.engagementAreas.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
})
}
I tried creating a variable, an array or object and equaling that to the return value and then returning that near the end of the scope but this wouldnt let me it said the return type was wrong.
Any help would be greatly appreciated.
I think your initial code, with the forEach and map separated was very clean!
The problem with your new code, is that using return inside .forEach() does not make sense in javascript ;) Whenever you return from .forEach(), javascript just ignores the result:
let result = [1,2,3].forEach(x => {
return x * 2;
});
result // undefined
I think you wanted to only return some area-s, which you can do with array.filter() like this:
productGroupPages(): PDF.Page[] {
const descriptions = {};
this.areas.forEach(area => descriptions[area.id] = area.description);
return this.json.engagementAreas
.filter(area => area.engagementTypes.length !== 0)
.map(area => {
return this.productGroupPage(area, descriptions[area.id]);
});
}
I hope that is actually what you meant to do, and if so, I hope this works :)

Categories