In JavaScript, how can I conditionally assign value to object with destructuring and short circuit evaluation?

110 views Asked by At

Let's say I have this set up:

const objA = { name: "Jacob", email: "[email protected]" };
const objB = { lastName: "Smith" };

Why can I do this:

const lastName = objA.lastName || objB.lastName;

But not this?

const { lastName } = objA || objB;

Was able to re-create above example in dev tools.

My real world application: I have "normal" and "legacy" account roles, and after querying both types of roles, I'd like to be able to do:

const { schoolAdmin } = account || legacyAccount;

... but instead have to do:

const schoolAdmin = account.schoolAdmin || legacyAccount.schoolAdmin;

Which is admittedly not a big deal, but I feel like there's something I'm missing and that I could use destructuring here. Jr dev, sorry if this is a dummy question! (Sometimes there is no account, and sometimes there is an account that doesn't have the schoolAdmin role! Likewise with legacyAccount.)

3

There are 3 answers

4
gilly3 On BEST ANSWER

Your expression is trying to assign lastName from a property in the object returned from the sub-expression on the right side of the = assignment operator. That right side sub-expression, objA || objB evaluates to objA, which has no lastName property, so your lastName variable receives a value of undefined.

You could either just use objA, and provide a default value from objB, if the property doesn't exist in objA:

const { lastName = objB.lastName } = objA;

Or, you can spread objA and objB into a single object, and destructure from that:

const { lastName } = { ...objB, ...objA };

When spreading, reverse the order. The last object spread overwrites values from the first object. So, if you want to prefer the value from objA, list objA last.

This spreading option provides a satisfying symmetry, but is likely to be less efficient. From a performance perspective, the first option is better. But, for me, personally, I think the code you don't like provides better clarity with performance as good as you can get:

const lastName = objA.lastName || objB.lastName;

This is completely unambiguous and performs no unnecessary operations.

It should also be pointed out that neither of my solutions is strictly equivalent to the non-destructuring approach. If objA.lastName is a falsy value besides undefined, ("", null, 0, or false), then objB.lastName would be used, whereas in both of my solutions, the falsey objA.lastName would be used. Thanks to VLAZ for mentioning this in the comments.

0
tsgrgo On

What Happens

The reason why you can't, is because an empty object returns true. Try !!{} in devtools for example, it returns true.

Knowing this, it's easy to realize that the || operator will always select the first object.

To test this, you can try the following:

const objA = { name: "Jacob", email: "[email protected]" };
const objB = { lastName: "Smith" };
let { lastName } = objB || objA; // Returns Smith

And:

const objA = { name: "Jacob", email: "[email protected]" };
const objB = { lastName: "Smith" };
let { lastName } = objA || objB; // Returns undefined

A Proposed Solution

The solution to this is to spread the two object into one. Note that the order of the two matters, as explained below!

const { lastName } = { ...objB, ...objA };
  • If objA has a lastName property, it will be used regardless if objB has the property, because it will just overwrite it.

  • If objA doesn't have the property, lastName will not be overwritten from objB, so its value will be returned. (Or undefined if none of them has the property)

7
Konrad On

As explained in the comments || doesn't evaluate to union of objects, it returns the second one

You can do this instead:

const { lastName } = { ...objA, ...objB };

It does create a union