Is adding a value inside an array.map considered mutating? - javascript

I know redux is strict with state management, but is adding a value from an array of objects considered a no no in redux? Example:
// Consider this array of objects on action
action.arr = [ { test: 'me', hail: 'hydra'}, { test: 'you', ring: 'of fire'} ]
// reducer.js
fn = (state = defaultState, action) => {
...
case action.LORD_COMMANDER:
return action.arr.map(v => {
v.john = 'snow'
return v
})
...
}
Is this completely safe on my reducer or should I use Object.assign()?

I think better use Object.assign. Let's consider two examples
const arr = [ { test: 'me', hail: 'hydra'}, { test: 'you', ring: 'of fire'} ];
const arr1 = arr.map(v => {
v.john = 'snow'
return v;
});
console.log(arr, arr1);
As you can see each Object in two arrays have property john because we change Objects which have same reference that is not safe for previous Array., in example below you can see that only in second Array Objects have property john it is because we make copy of Object
const arr = [ { test: 'me', hail: 'hydra'}, { test: 'you', ring: 'of fire'} ];
const arr1 = arr.map(v => {
return Object.assign({}, v, { john: 'show' })
});
console.log(arr, arr1);

in this case you are making your state to be the array and since the array in the action is always going to be different then yes you are mutating the state. as long as the objects are not references the state will be completely mutated a sample of a dirty state is.
var objA = {};
var objB = {};
action.array = [objA, objB];
in this case when you do the map your reference to the array would be new but the reference to the objects would be the same. here it would be advised to use Object.assign or (...) operator inside of your map function. or better yet i would recommend looking into immutablejs to handle immutable data
https://facebook.github.io/immutable-js/

Related

Can destructuring an array be used to map properties of each elements?

Suppose there is an array like this:
const a = [ {p:1}, {p:2}, {p:3} ];
Is it possible to destructure this array in order to obtain p = [1, 2, 3] ?
Because this does not work :
const [ ...{ p } ] = a; // no error, same as const p = a.p;
// p = undefined;
Edit
In response to all the answers saying that I need to use Array.prototype.map, I am aware of this. I was simply wondering if there was a way to map during the destructuring process, and the answer is : no, I need to destructure the array itself, then use map as a separate step.
For example:
const data = {
id: 123,
name: 'John',
attributes: [{ id:300, label:'attrA' }, { id:301, label:'attrB' }]
};
function format(data) {
const { id, name, attributes } = data;
const attr = attributes.map(({ label }) => label);
return { id, name, attr };
}
console.log( format(data) };
// { id:123, name:'John', attr:['attrA', 'attrB'] }
I was simply wondering if there was a way, directly during destructuring, without using map (and, respectfully, without the bloated lodash library), to retrive all label properties into an array of strings.
Honestly I think that what you are looking for doesn't exist, normally you would map the array to create a new array using values from properties. In this specific case it would be like this
const p = a.map(element => element.p)
Of course, there are some packages that have many utilities to help, like Lodash's map function with the 'property' iteratee
you can destructure the first item like this :
const [{ p }] = a;
but for getting all values you need to use .map
and the simplest way might be this :
const val = a.map(({p}) => p)
Here's a generalized solution that groups all properties into arrays, letting you destructure any property:
const group = (array) => array.reduce((acc,obj) => {
for(let [key,val] of Object.entries(obj)){
acc[key] ||= [];
acc[key].push(val)
}
return acc
}, {})
const ar = [ {p:1}, {p:2}, {p:3} ];
const {p} = group(ar)
console.log(p)
const ar2 = [{a:2,b:1},{a:5,b:4}, {c:1}]
const {a,b,c} = group(ar2)
console.log(a,b,c)

Creating a JavaScript function that filters out duplicate in-memory objects?

Okay, so I am trying to create a function that allows you to input an array of Objects and it will return an array that removed any duplicate objects that reference the same object in memory. There can be objects with the same properties, but they must be different in-memory objects. I know that objects are stored by reference in JS and this is what I have so far:
const unique = array => {
let set = new Set();
return array.map((v, index) => {
if(set.has(v.id)) {
return false
} else {
set.add(v.id);
return index;
}
}).filter(e=>e).map(e=>array[e]);
}
Any advice is appreciated, I am trying to make this with a very efficient Big-O. Cheers!
EDIT: So many awesome responses. Right now when I run the script with arbitrary object properties (similar to the answers) and I get an empty array. I am still trying to wrap my head around filtering everything out but on for objects that are referenced in memory. I am not positive how JS handles objects with the same exact key/values. Thanks again!
Simple Set will do the trick
let a = {'a':1}
let b = {'a': 1,'b': 2, }
let c = {'a':1}
let arr = [a,b,c,a,a,b,b,c];
function filterSameMemoryObject(input){
return new Set([...input])
}
console.log(...filterSameMemoryObject(arr))
I don't think you need so much of code as you're just comparing memory references you can use === --> equality and sameness .
let a = {'a':1}
console.log(a === a ) // return true for same reference
console.log( {} === {}) // return false for not same reference
I don't see a good reason to do this map-filter-map combination. You can use only filter right away:
const unique = array => {
const set = new Set();
return array.filter(v => {
if (set.has(v.id)) {
return false
} else {
set.add(v.id);
return true;
}
});
};
Also if your array contains the objects that you want to compare by reference, not by their .id, you don't even need to the filtering yourself. You could just write:
const unique = array => Array.from(new Set(array));
The idea of using a Set is nice, but a Map will work even better as then you can do it all in the constructor callback:
const unique = array => [...new Map(array.map(v => [v.id, v])).values()]
// Demo:
var data = [
{ id: 1, name: "obj1" },
{ id: 3, name: "obj3" },
{ id: 1, name: "obj1" }, // dupe
{ id: 2, name: "obj2" },
{ id: 3, name: "obj3" }, // another dupe
];
console.log(unique(data));
Addendum
You speak of items that reference the same object in memory. Such a thing does not happen when your array is initialised as a plain literal, but if you assign the same object to several array entries, then you get duplicate references, like so:
const obj = { id: 1, name: "" };
const data = [obj, obj];
This is not the same thing as:
const data = [{ id: 1, name: "" }, { id: 1, name: "" }];
In the second version you have two different references in your array.
I have assumed that you want to "catch" such duplicates as well. If you only consider duplicate what is presented in the first version (shared references), then this was asked before.

React How to merge an array and an array of objects

I have this two states:
stateOne: [
'marca01',
'marca02'
]
stateTwo: [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman'
}
]
And I'm using lodash to merge both in a third state, but as you see the first state is an array and the second one is an array of objects so I can't merge these two like I want to...
Something like this:
stateThree: [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men',
MARK:'marca01'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman',
MARK:'marca02'
}
]
How can I get the desired result (is not necessary to use lodash)
Without lodash :
const state3 = state2.map((product, index) => ({
...product,
MARK: state1[index]
}));
Here .map returns a new array whose new values are returned by the anonymous function.
The spread operator ...product flattens the previous object properties in the new object. This syntaxic sugar can be replaced by Object.assign.
Then we add a new MARK property based on the other state with the same index.
I used the map method and the spread operator in order to prevent state mutation which can be harmful in React.
You could iterate over the second array, and add the properties.
let s1 = [
'marca01',
'marca02'
]
let s2 = [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman'
}]
s2.forEach((x,i) => x.MARK = s1[i])
console.log(s2)
You can do like this:
stateThree = stateTwo.map( (element, i) => ({...element, MARK: stateOne[i] } ))

using es6 reduce() to manipulate an object using another object

I have state object as:
this.state = {
letterType: {},
letterTag: {},
departmentName: {},
assignedOfficer: {}
}
and I have an another object sortFilters as:
sortFilters = {
letterType: {
0: "letterType1",
1: "letterType2"
},
letterTag: {},
departmentName: {
0: "dept1"
},
assignedOfficer: {}
}
now what I want is to create a newState object (probably using es6 reduce()) which will be created based on sortFilters object such as:
this.newState = {
letterType: {
letterType1: true,
letterType2: true
},
letterTag: {},
departmentName: {
dept1: true
},
assignedOfficer: {}
}
I think this is possible using es6 reduce() but I am not able to get it to work.
As described, your problem is a use case for Object.assign, since you just want to copy the contents of four objects (on sortFilters) into your state object. You'd either do it manually:
Object.assign(this.newState.letterType, sortFilters.letterType);
Object.assign(this.newState.letterTag, sortFilters.letterTag);
Object.assign(this.newState.departmentName, sortFilters.departmentName);
Object.assign(this.newState.assignedOfficer, sortFilters.assignedOfficer);
...or in a loop:
for (const name of Object.keys(sortFilters)) {
Object.assign(this.newState[name], sortFilters[name]);
}
That merges the entries from the sortFilters objects with the ones in this.newState. If you want to replace them instead, you'd use assignment (and probably a shallow copy, but that depends on how sortFilters is used later):
this.newState.letterType = Object.assign({}, sortFilters.letterType);
this.newState.letterTag = Object.assign({}, sortFilters.letterTag);
this.newState.departmentName = Object.assign({}, sortFilters.departmentName);
this.newState.assignedOfficer = Object.assign({}, sortFilters.assignedOfficer);
or
for (const name of Object.keys(sortFilters)) {
this.newState[name] = Object.assign({}, sortFilters[name]);
}
Note that Object.assign does a shallow copy; if any of these objects are nested, you'll need something else.
As of ES2018, you can use property spread when creating the new objets instead of Object.assign:
for (const name of Object.keys(sortFilters)) {
this.newState[name] = {...sortFilters[name]};
}
There are other methods to achieve the same, as reduce is used over arrays. But if using reduce is a practice, then Yes that is also possible, a rough way could be like the following, where we can use reduce over keys of objects, which is an array.
let state = {letterType: {},letterTag: {},departmentName: {},assignedOfficer: {}}
let sortFilters = {letterType: {0: "letterType1",1: "letterType2"},letterTag: {},departmentName: {0: "dept1"},assignedOfficer: {}}
let newState = Object.keys(state).reduce(function(prev, current) {
let val = sortFilters[current]
if (!val) {
prev[current] = state[current]
} else {
prev[current] = Object.keys(val).reduce(function (p, c) {
p[val[c]] = true
return p
}, {})
}
return prev
}, {})
console.log(newState)
For more details about reduce and Object.keys, please refer to Mozilla Developer Network's documentation.
Not sure what "state" or this.state means in this context...React? Anyhow, tt looks like you just want to simply unpack certain properties and manipulate them. If so destructuring assignment might help. Refer to this article section on Destructuring Nested Objects and this section on Nested object and array destructuring
Demo
let sortFilters = {
letterType: {
0: "letterType1",
1: "letterType2"
},
letterTag: {},
departmentName: {
0: "dept1"
},
assignedOfficer: {}
}
// Making a copy of sortFilters
let final = JSON.parse(JSON.stringify(sortFilters));
// Desruturing final, assigning variables and values
let {
letterType: {
letterType1: letterType1 = true,
letterType2: letterType2 = true
},
letterTag: {},
departmentName: {
dept1: dept1 = true
},
assignedOfficer: {}
} = final;
console.log(letterType1);
console.log(letterType2);
console.log(dept1);

Remove value from object without mutation

What's a good and short way to remove a value from an object at a specific key without mutating the original object?
I'd like to do something like:
let o = {firstname: 'Jane', lastname: 'Doe'};
let o2 = doSomething(o, 'lastname');
console.log(o.lastname); // 'Doe'
console.log(o2.lastname); // undefined
I know there are a lot of immutability libraries for such tasks, but I'd like to get away without a library. But to do this, a requirement would be to have an easy and short way that can be used throughout the code, without abstracting the method away as a utility function.
E.g. for adding a value I do the following:
let o2 = {...o1, age: 31};
This is quite short, easy to remember and doesn't need a utility function.
Is there something like this for removing a value? ES6 is very welcome.
Thank you very much!
Update:
You could remove a property from an object with a tricky Destructuring assignment:
const doSomething = (obj, prop) => {
let {[prop]: omit, ...res} = obj
return res
}
Though, if property name you want to remove is static, then you could remove it with a simple one-liner:
let {lastname, ...o2} = o
The easiest way is simply to Or you could clone your object before mutating it:
const doSomething = (obj, prop) => {
let res = Object.assign({}, obj)
delete res[prop]
return res
}
Alternatively you could use omit function from lodash utility library:
let o2 = _.omit(o, 'lastname')
It's available as a part of lodash package, or as a standalone lodash.omit package.
With ES7 object destructuring:
const myObject = {
a: 1,
b: 2,
c: 3
};
const { a, ...noA } = myObject;
console.log(noA); // => { b: 2, c: 3 }
one line solution
const removeKey = (key, {[key]: _, ...rest}) => rest;
Explanations:
This is a generic arrow function to remove a specific key. The first argument is the name of the key to remove, the second is the object from where you want to remove the key. Note that by restructuring it, we generate the curated result, then return it.
Example:
let example = {
first:"frefrze",
second:"gergerge",
third: "gfgfg"
}
console.log(removeKey('third', example))
/*
Object {
first: "frefrze",
second: "gergerge"
}
*/
To add some spice bringing in Performance. Check this thread bellow
https://github.com/googleapis/google-api-nodejs-client/issues/375
The use of the delete operator has performance negative effects for
the V8 hidden classes pattern. In general it's recommended do not use
it.
Alternatively, to remove object own enumerable properties, we could
create a new object copy without those properties (example using
lodash):
_.omit(o, 'prop', 'prop2')
Or even define the property value to null or undefined (which is
implicitly ignored when serializing to JSON):
o.prop = undefined
You can use too the destructing way
const {remov1, remov2, ...new} = old;
old = new;
And a more practical exmple:
this._volumes[this._minCandle] = undefined;
{
const {[this._minCandle]: remove, ...rest} = this._volumes;
this._volumes = rest;
}
As you can see you can use [somePropsVarForDynamicName]: scopeVarName syntax for dynamic names. And you can put all in brackets (new block) so the rest will be garbage collected after it.
Here a test:
exec:
Or we can go with some function like
function deleteProps(obj, props) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
for typescript
function deleteProps(obj: Object, props: string[]) {
if (!Array.isArray(props)) props = [props];
return Object.keys(obj).reduce((newObj, prop) => {
if (!props.includes(prop)) {
newObj[prop] = obj[prop];
}
return newObj;
}, {});
}
Usage:
let a = {propH: 'hi', propB: 'bye', propO: 'ok'};
a = deleteProps(a, 'propB');
// or
a = deleteProps(a, ['propB', 'propO']);
This way a new object is created. And the fast property of the object is kept. Which can be important or matter. If the mapping and the object will be accessed many many times.
Also associating undefined can be a good way to go with. When you can afford it. And for the keys you can too check the value. For instance to get all the active keys you do something like:
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k] !== undefined);
//or
const allActiveKeys = Object.keys(myObj).filter(k => myObj[k]); // if any false evaluated value is to be stripped.
Undefined is not suited though for big list. Or development over time with many props to come in. As the memory usage will keep growing and will never get cleaned. So it depend on the usage. And just creating a new object seem to be the good way.
Then the Premature optimization is the root of all evil will kick in. So you need to be aware of the trade off. And what is needed and what's not.
Note about _.omit() from lodash
It's removed from version 5. You can't find it in the repo. And here an issue that talk about it.
https://github.com/lodash/lodash/issues/2930
v8
You can check this which is a good reading https://v8.dev/blog/fast-properties
As suggested in the comments above if you want to extend this to remove more than one item from your object I like to use filter. and reduce
eg
const o = {
"firstname": "Jane",
"lastname": "Doe",
"middlename": "Kate",
"age": 23,
"_id": "599ad9f8ebe5183011f70835",
"index": 0,
"guid": "1dbb6a4e-f82d-4e32-bb4c-15ed783c70ca",
"isActive": true,
"balance": "$1,510.89",
"picture": "http://placehold.it/32x32",
"eyeColor": "green",
"registered": "2014-08-17T09:21:18 -10:00",
"tags": [
"consequat",
"ut",
"qui",
"nulla",
"do",
"sunt",
"anim"
]
};
const removeItems = ['balance', 'picture', 'tags']
console.log(formatObj(o, removeItems))
function formatObj(obj, removeItems) {
return {
...Object.keys(obj)
.filter(item => !isInArray(item, removeItems))
.reduce((newObj, item) => {
return {
...newObj, [item]: obj[item]
}
}, {})
}
}
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
My issue with the accepted answer, from an ESLint rule standard, if you try to destructure:
const { notNeeded, alsoNotNeeded, ...rest } = { ...ogObject };
the 2 new variables, notNeeded and alsoNotNeeded may throw a warning or error depending on your setup since they are now unused. So why create new vars if unused?
I think you need to use the delete function truly.
export function deleteKeyFromObject(obj, key) {
return Object.fromEntries(Object.entries(obj).filter(el => el[0] !== key))
}
with lodash cloneDeep and delete
(note: lodash clone can be used instead for shallow objects)
const obj = {a: 1, b: 2, c: 3}
const unwantedKey = 'a'
const _ = require('lodash')
const objCopy = _.cloneDeep(obj)
delete objCopy[unwantedKey]
// objCopy = {b: 2, c: 3}
For my code I wanted a short version for the return value of map() but the multiline/mutli operations solutions were "ugly". The key feature is the old void(0) which resolve to undefined.
let o2 = {...o, age: 31, lastname: void(0)};
The property stays in the object:
console.log(o2) // {firstname: "Jane", lastname: undefined, age: 31}
but the transmit framework kills it for me (b.c. stringify):
console.log(JSON.stringify(o2)) // {"firstname":"Jane","age":31}
I wrote big function about issue for me. The function clear all values of props (not itself, only value), arrays etc. as multidimensional.
NOTE: The function clear elements in arrays and arrays become an empty array. Maybe this case can be added to function as optional.
https://gist.github.com/semihkeskindev/d979b169e4ee157503a76b06573ae868
function clearAllValues(data, byTypeOf = false) {
let clearValuesTypeOf = {
boolean: false,
number: 0,
string: '',
}
// clears array if data is array
if (Array.isArray(data)) {
data = [];
} else if (typeof data === 'object' && data !== null) {
// loops object if data is object
Object.keys(data).forEach((key, index) => {
// clears array if property value is array
if (Array.isArray(data[key])) {
data[key] = [];
} else if (typeof data[key] === 'object' && data !== null) {
data[key] = this.clearAllValues(data[key], byTypeOf);
} else {
// clears value by typeof value if second parameter is true
if (byTypeOf) {
data[key] = clearValuesTypeOf[typeof data[key]];
} else {
// value changes as null if second parameter is false
data[key] = null;
}
}
});
} else {
if (byTypeOf) {
data = clearValuesTypeOf[typeof data];
} else {
data = null;
}
}
return data;
}
Here is an example that clear all values without delete props
let object = {
name: 'Semih',
lastname: 'Keskin',
brothers: [
{
name: 'Melih Kayra',
age: 9,
}
],
sisters: [],
hobbies: {
cycling: true,
listeningMusic: true,
running: false,
}
}
console.log(object);
// output before changed: {"name":"Semih","lastname":"Keskin","brothers":[{"name":"Melih Kayra","age":9}],"sisters":[],"hobbies":{"cycling":true,"listeningMusic":true,"running":false}}
let clearObject = clearAllValues(object);
console.log(clearObject);
// output after changed: {"name":null,"lastname":null,"brothers":[],"sisters":[],"hobbies":{"cycling":null,"listeningMusic":null,"running":null}}
let clearObject2 = clearAllValues(object);
console.log(clearObject2);
// output after changed by typeof: {"name":"","lastname":"","brothers":[],"sisters":[],"hobbies":{"cycling":false,"listeningMusic":false,"running":false}}

Categories