Related
I would like to add or delete object to another object through react state. How can I achieve this?
code is like this:
const [ stateObj, setStateObj ] = usetState({
obj1: {},
obj2: {},
obj3: {}
})
I want to add some objects temp1,temp2,temp3,... inside obj1, such as:
setState({ ...stateObj, obj1: {...obj1,temp1} })
setState({ ...stateObj, obj1: {...obj1,temp2} })
...
but this seems not correct, this keeps obj1 one item only
how can I add temp1,temp2,... into obj1 by state?
and also the method for delete temp1, temp2,...?
be careful we should copy the state first:
Never mutate state directly, as calling setState() afterwards may replace the mutation you made. Treat state as if it were immutable.
const temp1 = { a: 1 };
const temp2 = { b: 2, c: 3 };
const prevState={...stateObj};
const newObj1 = { ...prevState.obj1, ...temp1, ...temp2 };
setStateObj({ ...prevState, obj1: newObj1 });
console.log(stateObj);
here is an example just with javascript
const stateObj = {
obj1: {},
obj2: {},
obj3: {}
};
const temp1={a:1};
const temp2={b:2,c:3};
const newObj1={...stateObj.obj1,...temp1,...temp2};
const updatedState={...stateObj,obj1:newObj1}
console.log(updatedState)
You're calling setState simultaneously, so the state is updated only once with the last value. setState is batched together as it is not synchronous. You have to update the state with the previous state
You could try this code:
setStateObj(prevState = > {
return {
...prevState,
obj1: {
...obj1,
temp1
}
}
})
I have a nested object. I need to filter them out by property of the child object but only get the keys.
I have tried so far to first, inject a property id into each child object and assign the object's key as its value. Then proceed to filter the object, compare property if it will match with the query, then return the injected property id.
let test_obj = {
A: {
a: 1,
b: 1,
},
B: {
a: 1,
b: 2,
},
C: {
a: 1,
b: 3,
}
}
let identify = (e) => {
for (e of Object.entries(e)){
key = e[0];
val = e[1];
val.id = key;
console.log(e);
}
}
identify(test_obj);
let query = (test_obj,prop,val) => (Object.values(test_obj).filter(o => o[prop] == val).map(o=>o.id));
let result = query(test_obj,"b",2);
console.log(result)
It currently return my desired results, yet I feel like I cheated. Is there a way to do this without having to inject another property to determine the key? I feel like I'm missing something, but I can't wrap my head around this.
Instead of adding an additional key, and then filtering values, you can filter the keys like this instead:
const test_obj = {
A: {
a: 1,
b: 1,
},
B: {
a: 1,
b: 2,
},
C: {
a: 1,
b: 3,
}
}
const query = (obj, prop, val) => Object.keys(obj).filter(k => obj[k][prop] === val);
console.log(query(test_obj, "b", 2))
A more elegant solution is to use the reduce functionality, which you can (and always should) use if you find yourself using filter and map:
function findKeysForValue(test_obj, value) {
return Object.entries(test_obj).reduce((myKeys, [objKey, outerValue]) => {
if (Object.values(outerValue).find(nestedValue => nestedValue === value)) {
return [...myKeys, objKey];
}
return myKeys;
}, []);
}
I am looking for a simple way to do the following. I have tried to do this with lodash.reduce and it is clunky, is there an easier way.
From:
[{a: 'meow'}, {a: 'woof'}]
To:
{a: ['meow', 'woof']}
You can do that with pure JS, no need of loadash.
Call the reduce method of arrays on your input array, and reduce the array to an object, looping over the keys of your inner objs:
const input = [{a: 'meow'}, {a: 'woof'}, {b: 'hi'}, {a: 'dog', c: 'bye'}, {}];
console.log(input.reduce((acc, val) => {
Object.keys(val).forEach(key => {
if(!acc[key]) {
acc[key] = [];
}
acc[key].push(val[key]);
});
return acc;
}, {}));
You can use lodash#assignWith to assign all properties their respective values into one object, together with a customizer function to determine how you want to structure the object.
const result = _.assignWith({}, ...data, (v = [], s) => v.concat(s));
Note: To make sure that we don't mutate any of the objects in the data array, I passed an empty object as the first parameter to act as the destination object.
const data = [
{ a: 'meow' },
{ a: 'woof', k: 'hey' },
{ k: 'yo', d: 'hehe' },
{ d: 'wazup', q: 'ohoho' }
];
const result = _.assignWith({}, ...data, (v = [], s) => v.concat(s));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
I had some issues with typescript and lodash.reduce, this worked.
export function getFuncsObject(funcs): Record<Funcs, Case[]> {
let f = { ...funcs };
f = lodash.mapValues(f, () => []);
return f;
}
export function mockMerge(funcs, mocks: Record<Funcs, Case | null>[]): Record<Funcs, Case[]> {
const f = getFuncsObject(funcs);
lodash.each(mocks, (v, k) => {
f[k].push(v);
});
return f;
}
One option would be to use two reductions as follows:
const input = [{
a: 'meow'
}, {
a: 'woof'
}, {
b: 'moo'
}];
const result = input
.reduce((itemResult, item) => Object.keys(item)
.reduce((keyResult, key) => ({
...keyResult,
[key]: (keyResult[key] || []).concat(item[key])
}), itemResult), {});
console.log(result)
Not sure if this is clunky compared to your current solution, but it's fairly concise and does not require an external library.
Without using any external libraries or reduce.
const input = [ {a: 'meow'}, {a: 'woof'}, {b: 'hi'}, {a: 'dog', c: 'bye'}, {} ];
let output = {};
input.forEach((inputObj) => {
for(let key in inputObj){
if(!output[ key ]){
output[ key ] = [];
}
output[ key ].push(inputObj[key])
}
});
console.log(output);
Consider a function returns an nested object and I want to modify the property inside the nested object.
In the below example, I'm calling the function many times or I need to store it in a temporary variable. Is there a way to invoke only once inside the braces and spread/modify inside the same object many times.
const getObject = () => {
return {
a: {
b: {
c: 1,
d: 2,
}
},
e: 3
}
}
var modifiedD = {
...getObject(),
a: {
b: {
...getObject().a.b,
d: 4
}
}
}
console.log(modifiedD);
when declaring a key after ...getObject() it replace the whole value. It does not merge the inner object behind a.
So you could do it as you have done and call getObject() multiple time.
An other solution could be to handle it using a function of your own merging the objects, like :
function mergeObjects(obj1, obj2) {
// We are going to copy the value of each obj2 key into obj1
Object.keys(obj2).forEach((x) => {
// If we have an object, we go deeper
if (typeof obj2[x] === 'object') {
if (obj1[x] === void 0) {
obj1[x] = {};
}
mergeObjects(obj1[x], obj2[x]);
} else {
obj1[x] = obj2[x];
}
});
return obj1;
}
const getObject = () => {
return {
a: {
b: {
c: 1,
d: 2,
}
},
e: 3
}
}
const modifiedD = mergeObjects(getObject(), {
a: {
b: {
d: 4,
},
},
});
console.log(modifiedD);
WARNING, the function I have made mutate the object which may not be the best answer
Or call it only once and then set the keys one by one like :
const getObject = () => {
return {
a: {
b: {
c: 1,
d: 2,
}
},
e: 3
}
}
const modifiedD = getObject();
modifiedD.a.b.d = 4;
console.log(modifiedD);
Further to my previous answer, as Grégory NEUT pointed out you could have a lot larger complexity.
If so, you could simply create two objects and then merge them. I found a function code snippet to be able to do that using Object.assign
Example:
const getObject = () => {
return {
a: {
b: {
c: 1,
d: 2,
}
},
e: 3
}
}
var modifiedD = getObject();
var newD = {
a: {
b: {
d: 4
},
y: 1
},
z: 20
}
/** TAKEN FROM https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6 **/
// Merge a `source` object to a `target` recursively
const merge = (target, source) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (let key of Object.keys(source)) {
if (source[key] instanceof Object) Object.assign(source[key], merge(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
modifiedD = merge(modifiedD, newD);
console.log(modifiedD);
You can try the following:
getParentObj(path, obj) {
return path.split('.').reduce((o,i)=>o[i], obj);
}
const parent = getParentObj('a.b', getObject());
parent[d] = 24;
I am using Redux. In my reducer I'm trying to remove a property from an object like this:
const state = {
a: '1',
b: '2',
c: {
x: '42',
y: '43'
},
}
And I want to have something like this without having to mutate the original state:
const newState = {
a: '1',
b: '2',
c: {
x: '42',
},
}
I tried:
let newState = Object.assign({}, state);
delete newState.c.y
but for some reasons, it deletes the property from both states.
Could help me to do that?
How about using destructuring assignment syntax?
const original = {
foo: 'bar',
stack: 'overflow',
};
// If the name of the property to remove is constant
const { stack, ...withoutFirst } = original;
console.log(withoutFirst); // Will be { "foo": "bar" }
// If the name of the property to remove is from a variable
const key = 'stack'
const { [key]: value, ...withoutSecond } = original;
console.log(withoutSecond); // Will be { "foo": "bar" }
// To do a deep removal with property names from variables
const deep = {
foo: 'bar',
c: {
x: 1,
y: 2
}
};
const parentKey = 'c';
const childKey = 'y';
// Remove the 'c' element from original
const { [parentKey]: parentValue, ...noChild } = deep;
// Remove the 'y' from the 'c' element
const { [childKey]: removedValue, ...childWithout } = parentValue;
// Merge back together
const withoutThird = { ...noChild, [parentKey]: childWithout };
console.log(withoutThird); // Will be { "foo": "bar", "c": { "x": 1 } }
I find ES5 array methods like filter, map and reduce useful because they always return new arrays or objects. In this case I'd use Object.keys to iterate over the object, and Array#reduce to turn it back into an object.
return Object.assign({}, state, {
c: Object.keys(state.c).reduce((result, key) => {
if (key !== 'y') {
result[key] = state.c[key];
}
return result;
}, {})
});
You can use _.omit(object, [paths]) from lodash library
path can be nested for example: _.omit(object, ['key1.key2.key3'])
Just use ES6 object destructuring feature
const state = {
c: {
x: '42',
y: '43'
},
}
const { c: { y, ...c } } = state // generates a new 'c' without 'y'
console.log({...state, c }) // put the new c on a new state
That's because you are copying the value of state.c to the other object. And that value is a pointer to another javascript object. So, both of those pointers are pointing to the same object.
Try this:
let newState = Object.assign({}, state);
console.log(newState == state); // false
console.log(newState.c == state.c); // true
newState.c = Object.assign({}, state.c);
console.log(newState.c == state.c); // now it is false
delete newState.c.y;
You can also do a deep-copy of the object. See this question and you'll find what's best for you.
How about this:
function removeByKey (myObj, deleteKey) {
return Object.keys(myObj)
.filter(key => key !== deleteKey)
.reduce((result, current) => {
result[current] = myObj[current];
return result;
}, {});
}
It filters the key that should be deleted then builds a new object from the remaining keys and the initial object. The idea is stolen from Tyler McGinnes awesome reactjs program.
JSBin
function dissoc(key, obj) {
let copy = Object.assign({}, obj)
delete copy[key]
return copy
}
Also, if looking for a functional programming toolkit, look at Ramda.
As of 2019, another option is to use the Object.fromEntries method. It has reached stage 4.
const newC = Object.fromEntries(
Object.entries(state.c).filter(([key]) => key != 'y')
)
const newState = {...state, c: newC}
The nice thing about it is that it handles integer keys nicely.
You may use Immutability helper in order to unset an attribute, in your case:
import update from 'immutability-helper';
const updatedState = update(state, {
c: {
$unset: ['y']
}
});
It's easy with Immutable.js:
const newState = state.deleteIn(['c', 'y']);
description of deleteIn()
Here's an easy 1-liner you can use that allows you to partially apply the prop you want to remove. This makes it easy to pass to Array.map.
const removeProp = prop => ({ [prop]: _, ...rest }) => ({ ...rest })
Now you can use it like this:
const newArr = oldArr.map(removeProp('deleteMe'))
The issue you are having is that you are not deep cloning your initial state. So you have a shallow copy.
You could use spread operator
const newState = { ...state, c: { ...state.c } };
delete newState.c.y
Or following your same code
let newState = Object.assign({}, state, { c: Object.assign({}, state.c) });
delete newState.c.y
I normally use
Object.assign({}, existingState, {propToRemove: undefined})
I realise this isn't actually removing the property but for almost all purposes 1 its functionally equivalent. The syntax for this is much simpler than the alternatives which I feel is a pretty good tradeoff.
1 If you are using hasOwnProperty(), you will need to use the more complicated solution.
I use this pattern
const newState = Object.assign({}, state);
delete newState.show;
return newState;
but in book i saw another pattern
return Object.assign({}, state, { name: undefined } )
utility ;))
const removeObjectField = (obj, field) => {
// delete filter[selectName]; -> this mutates.
const { [field]: remove, ...rest } = obj;
return rest;
}
action type
const MY_Y_REMOVE = 'MY_Y_REMOVE';
action creator
const myYRemoveAction = (c, y) => {
const result = removeObjectField(c, y);
return dispatch =>
dispatch({
type: MY_Y_REMOVE,
payload: result
})
}
reducer
export default (state ={}, action) => {
switch (action.type) {
case myActions.MY_Y_REMOVE || :
return { ...state, c: action.payload };
default:
return state;
}
};
As hinted in some of the answers already, it's because you are trying to modify a nested state ie. one level deeper. A canonical solution would be to add a reducer on the x state level:
const state = {
a: '1',
b: '2',
c: {
x: '42',
y: '43'
},
}
Deeper level reducer
let newDeepState = Object.assign({}, state.c);
delete newDeepState.y;
Original level reducer
let newState = Object.assign({}, state, {c: newDeepState});
Use a combination of Object.assign, JSON.parse and JSON.stringify
const obj1 = { a: "a", b: "b" };
const obj2 = { c: "c", a: undefined };
const merged = Object.assign({}, obj1, obj2);
const sanitized = JSON.parse(JSON.stringify(merged));
console.log(sanitized); // -> { b: "b", c: "c" }