destructing an object being function's argument with default property - javascript

I want to pass an object as an argument to the function. If property 'prop' of this object is undefined, it should be initialized by default value.
How to do it using modern JS?
I expect something like this:
const fun = function(options = { prop: 'default'}) { console.log(options.prop)}
fun({a: 'x', prop: 'hello'}) ... // log: 'hello'
fun({a: 'x'}) ... // log: 'default'

If you want to destructure in the parameter list, you won't have access to the entire original object (the options, here) anymore - you'll only have the destructured variables. So, leave out the options = part, and put the = after the prop, instead of :. For example:
const fun = function({ prop = 'default', a }) {
console.log('prop:', prop, 'a:', a);
};
fun({
a: 'x',
prop: 'hello'
}) // log: 'hello'
fun({
a: 'x'
}) // log: 'default'
If the function also may not be called with any parameters, you may default-assign an empty object:
const fun = function({ prop = 'default', a } = {}) {
console.log('prop:', prop, 'a:', a);
};
fun({
a: 'x',
prop: 'hello'
}) // log: 'hello'
fun({
a: 'x'
}) // log: 'default'
fun(); // log: 'default'

Use this syntax instead:
const fun = ({ prop = "default" }) => console.log(prop);
fun({ a: "x", prop: "hello" });
fun({ a: "x" });
Note that you will lose the object, and only have access to prop. To solve this, you can collect the rest of the properties and expand them into an object:
const fun = ({ prop = "default", ...rest }) => {
console.log(prop);
const otherProps = { ...rest };
console.log(JSON.stringify(otherProps));
}
fun({ a: "x", prop: "hello" });
fun({ a: "x" });
fun({ a: "x", b: "nonexistent", c: true, prop: "Hi!" });

You could use the rest properties and exclude prop from the object, take the value or default and assign this value back to the object.
const
fun = function({ prop = 'default', ...options }) {
Object.assign(options, { prop });
console.log(options.prop);
};
fun({ a: 42 });
fun({ a: 42, prop: 'bar' });

I think you have to handle this inside the function itself. Below is the code which will work for your particular scenario
var fun = function(options = { prop:'default'}) {
if(options.prop == undefined){
options.prop = 'default'
}
console.log(options.prop)
}
fun({a:'hello'})// logs: default

Related

If function parameter is empty is there a way to pass ...rest of values to that parameter?

If function parameter is empty is there a way to pass ...rest of the destructured values to that parameter with spread operator?
For an example:
const obj = {
/* param: {
a: 2,
b: 3
}, */
c: 1,
d: 3
}
const fun = ({ param = ...rest}) => {
console.log(param);
};
fun(obj);
In this case param is "undefined" and i would like to get the rest of the obj assign to the param {c:1, d:3}
In case when param is defined, I would like to have param data {a:2, b:3}
Use a ternary operator in the function parameter section to determine which properties to log:
const obj = {
/*param: {
a: 2,
b: 3
},*/
c: 1,
d: 3
}
const fun = (args = obj.param ? obj.param : obj) => {
console.log(args)
}
fun(obj)

Setting default values for each key in a deconstructed nested object in JavaScript

I know, generally, how to deconstruct a simple object in Javascript. I also know how to give default values to simple objects during deconstruction.
For example, I can pass two different nested objects to a logger function. Where the logger function simply console logs a nested array
var firstExample = { name: "foo", number: 1, myObject: { myArray: [1, 2, 3] } };
var secondExample = { name: undefined, number: undefined, myObject: { myArray: [9, 8, 7] } };
var myLogger = ({
name = "defaultName",
number = 999,
myObject: { myArray: stuff }
}) => {
console.log(`${name} ${stuff}`);
};
myLogger(firstExample); // "foo 1,2,3"
myLogger(secondExample); // "defaultName 9,8,7"
Currently, however, I'm struggle in deconstructing a nested objected and giving it values for each nested key/value pairing.
For example, when I try adding the following code using the same myLogger function a TypeError is thrown.
var myLogger = ({
name = "defaultName",
number = 999,
myObject: { myArray: stuff }
}) => {
console.log(`${name} ${stuff}`);
};
var thirdExample = { name: "bar", number: 2 };
myLogger(thirdExample) // ERROR
// TypeError: Cannot destructure property `myArray` of 'undefined' or 'null'.
How do I properly write my function to deconstruct the object to avoid this error?
As the error implies:
Cannot destructure property myArray of 'undefined' or 'null'.
The parent object (myObject) should have a default value of an empty object, so that a (possibly existing) property myArray can be extracted from it (and possibly assigned a default value):
var myLogger = ({
name = "defaultName",
number = 999,
myObject: { myArray: stuff } = {} // <-------
}) => {
console.log(`${name} ${stuff}`);
};
var thirdExample = { name: "bar", number: 2 };
myLogger(thirdExample) // ERROR
// TypeError: Cannot destructure property `myArray` of 'undefined' or 'null'.
To assign a default value as well:
var myLogger = ({
name = "defaultName",
number = 999,
myObject: { myArray: stuff = 'stuffDefault' } = {} // <-------
}) => {
console.log(`${name} ${stuff}`);
};
var thirdExample = { name: "bar", number: 2 };
myLogger(thirdExample) // ERROR
// TypeError: Cannot destructure property `myArray` of 'undefined' or 'null'.
Note that this will also throw if the function is called with no parameters:
var myLogger = ({
name = "defaultName",
number = 999,
myObject: { myArray: stuff = 'stuffDefault' } = {} // <-------
}) => {
console.log(`${name} ${stuff}`);
};
var thirdExample = { name: "bar", number: 2 };
myLogger()
So you might assign a default value to the whole parameter too:
var myLogger = ({
name = "defaultName",
number = 999,
myObject: { myArray: stuff = 'stuffDefault' } = {}
} = {}) => { // <-------
console.log(`${name} ${stuff}`);
};
var thirdExample = { name: "bar", number: 2 };
myLogger()

How to preserver object scope while using destructuring assignment?

Consider the following code:
const obj = {
a: 'foo',
b: function () { return this.a }
}
console.log(obj.a) // 'foo'
console.log(obj.b()) // 'foo'
const { a, b } = obj
console.log(a) // 'foo'
console.log(b()) // undefined
When I destruct obj, b() cannot access a anymore.
How can I preserve obj scope and allow b() to access it's siblings properties/methods while descructuring?
#dayan-moreno-leon posted a good option.
But I just noticed that I can be handled simply by using always static calls:
const obj = {
a: 'foo',
b: function () { return obj.a } //HERE! :D
}
const { a, b } = obj
console.log(obj.a) // 'foo'
console.log(obj.b()) // 'foo'
console.log(a) // 'foo'
console.log(b()) // 'foo'

Destructuring objects as function parameters deep extend

Is it possible to "deep extend" objects when using destructuring to set default properties of an object passed into a function?
Example:
function foo({
foo = 'foo',
bar = 'bar',
baz = {
propA: 'propA',
propB: 'propB'
}
} = {}) {
console.log(foo);
console.log(bar);
console.log(baz);
}
foo({
foo: 'changed',
baz: {
propA: 'changed'
}
});
This outputs: (baz is overwrote)
changed
bar
{
"propA": "changed"
}
Is there syntax which will extend the baz object, to give output:
changed
bar
{
"propA": "changed",
"propB": "propB"
}
There is no native-way to do what you ask, but you can write your own function decorator, of course it is a verbose approach...
Note: In order to perform a deep merge you should use some of the existing solution developed by the community, both Object.assign and Object Spread Operator perform a shallow copy of the source, even, writing your own deepmerge function could be error-prone. I suggest you to use lodash#merge that is commonly accepted as one of the solid solutions to the problem.
var {
// https://lodash.com/docs/#merge
merge
} = require('lodash');
function defaultsArgDecorator(defaults, target) {
return function(args) {
return target(
merge(Object.create(defaults), args)
)
}
}
function _foo(args) { console.log({args}); }
var foo = defaultsArgDecorator({
foo: 'foo',
bar: 'bar',
baz: {
propA: 'propA',
propB: 'propB'
}
}, _foo);
foo({bar: "ciao bello", baz: {propA: "HELLO"}})
Since parameters destructuring allow you to use previous parameters in the defaults of other parameters, you can create a property that doesn't exit on the original parameters, for example __baz, and set its defaults using baz.
In the method you'll use __baz instead of baz.
Note: this is a hack, and if the object contains a property by the
name of __baz it will override the default, with unexpected results.
However, you can name the default property with something like
dontUse__baz, which has a very low chance of being used.
Default properties using Object#assign:
function foo({
foo = 'foo',
bar = 'bar',
baz,
__baz = Object.assign({
"propA": "changed",
"propB": "propB"
}, baz)
} = {}) {
console.log(foo);
console.log(bar);
console.log(__baz);
}
foo({
foo: 'changed',
baz: {
propA: 'changed'
}
});
Default properties using object spread (requires babel plugin - see link):
function foo({
foo = 'foo',
bar = 'bar',
baz,
__baz = {
"propA": "changed",
"propB": "propB",
...baz
}
} = {}) {
console.log(foo);
console.log(bar);
console.log(__baz);
}
foo({
foo: 'changed',
baz: {
propA: 'changed'
}
});
You can also do nested destructuring. You destructure baz, but also destructure propA and propB.
If you need your object whole and don't want to 'rebuild' it, this isn't for you, but it does accomplish one objective of setting default values for unspecified properties nested in an object.
I've left baz as a separate descructured value in addition to it's properties as an example of a sometimes handy pattern, but you would most likely want to remove it since it suffers from the same problem of losing default properties.
function foo({
foo = 'foo',
bar = 'bar',
baz, // likely extraneous and confusing, here for example
baz: { propA = 'propA', propB = 'propB' }
} = {}) {
console.log(foo);
console.log(bar);
console.log(propA, propB);
console.log(baz);
console.log(Object.assign(baz, {propA, propB}));
}
foo({
foo: 'changed',
baz: {
propA: 'changed'
}
});
Yep, totally possible!
Solution 1:
function foo({
foo = 'foo',
bar = 'bar',
baz: {
propA = 'propA',
propB = 'propB'
} = {}
}) {
console.log(foo)
console.log(bar)
console.log({propA, propB}) // Because `bar` is renamed to `{propA, propB}`. Hence, no local variable named `baz`
}
Solution 2 (Workaround):
function foo({
foo = 'foo',
bar = 'bar',
baz = {}
}) {
// ...
const baz2 = Object.assign({}, {propA: 'propA', propB: 'propB'} = {})
console.log(baz2) // Yeah, not really a solution
}
You can even do this:
const func = ([{foo, bar: {a, b: BB, c: CC = 'c'} = {}} = {}], ...{0: rest0 = 0, 1: rest1 = '1'}) => { /* do stuffs */ }
The code above is a complicated example, I'll use simpler examples to explain:
Example 1:
const f1 = ({x: varx}) => console.log(varx)
When you call f1(obj), obj.x will be assigned to varx in f1's scope
Example 2:
const f2 = ({xy: {x, y}}) => console.log({x, y})
Similar to f1 but with {x, y} in place of varx, obj.xy will be assigned to {x, y}, so x = obj.xy.x and y = obj.xy.y
Example 3: Now let add create default parameters
const f3 = ({xy: {x, y} = {}}) => { /* do stuffs */ }
Now, you still have to pass obj as an object but you no longer have to provide obj.xy. If obj.xy is undefined, x and y are undefineds
Example 4: Now, let x and y have default values
const f4 = ({xy: {x = 'XXX', y = 'YYY'} = {}}) => { /* do stuffs */ }
If obj.xy you passed is undefined, {x, y} will be set to {x: undefined, y: undefined}, but since x and y also have default values, x will be 'XXX' and y will be 'YYY'
Example 4: "THE ULTIMATE LIFE FORM"
const f5 = ({xy: {x = 'XXX', y = 'YYY'} = {}} = {}) => { /* do stuffs */ }
Guess I don't have to explain this anymore :D

Remove a property in an object immutably

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" }

Categories