1

Let's say I have this set up:

const objA = { name: "Jacob", email: "jacob@email.com" };
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.)

fokicheva
  • 37
  • 5
  • 4
    `objA.lastName || objB.lastName;` -> if `objA.lastName` is truthy, use it. Otherwise use `objB.lastName`. `objA || objB` -> if `objA` is truthy use it. Otherwise use `objB` [Logical operators in JavaScript — how do you use them?](https://stackoverflow.com/q/4535647) | [JavaScript OR (||) variable assignment explanation](https://stackoverflow.com/q/2100758) | [What does the construct x = x || y mean?](https://stackoverflow.com/q/2802055) – VLAZ Dec 05 '22 at 16:46
  • 3
    `const { lastName } = objA || objB;`: `objA` is truthy, and so it is the result of the OR operator. And it doesn't have a `lastName` property, hence the destructured variable `lastName` remains `undefined`. – Ben Aston Dec 05 '22 at 16:49
  • Gotcha, these make sense, thank you both! Wish there was a shorthand to not have to repeat the lastName twice, but now I understand why it doesn't work. – fokicheva Dec 05 '22 at 16:51
  • 1
    @fokicheva an extremely simple shortcut would be to write a function. Then you don't have to repeat neither the property, nor the code again and again. Moreover, you're guaranteed that if you want to update something (e.g., you want to make it safe if either of the object is `null`), you won't have to hunt down all places and update each. – VLAZ Dec 05 '22 at 16:57

3 Answers3

2

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: "jacob@email.com" };
const objB = { lastName: "Smith" };
let { lastName } = objB || objA; // Returns Smith

And:

const objA = { name: "Jacob", email: "jacob@email.com" };
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)

tsgrgo
  • 384
  • 1
  • 4
  • 9
2

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

Konrad
  • 21,590
  • 4
  • 28
  • 64
  • OR returns the first truthy operand, or the second falsy operand – Ben Aston Dec 05 '22 at 16:53
  • 3
    Note that using union in this case MAY not be what the OP wants, however, as they want the lastName from objA if it exists, but unioning will override with lastName from objB if both exist, so probably the reverse would be preferable: `const { lastName } = { ...objB, ...objA };` – Blunt Jackson Dec 05 '22 at 16:55
  • YES, thank you for also providing a solution to my specific hang up. – fokicheva Dec 05 '22 at 16:56
  • Thank you @BluntJackson, this is also very important to know! Will change the variables arround to account for this. – fokicheva Dec 05 '22 at 16:58
  • Note this will *overwrite* whatever was in the first object. With case when `objA = { lastName: "foo" }` and `objB = { lastName: "bar" }` the result would be `"bar"` even though there was a value in objA while `objA.lastName || objB.lastName` will produce `"foo"` – VLAZ Dec 05 '22 at 17:00
  • @BluntJackson not necessarily, either. For `objA = { lastName: "" }` and `objB = { lastName: "bar" }` the result will be empty string rather than `"bar"`. – VLAZ Dec 05 '22 at 17:02
  • @VLAZ- that was not the OP's dataset, but it's a fair point. – Blunt Jackson Dec 05 '22 at 17:20
2

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.

gilly3
  • 87,962
  • 25
  • 144
  • 176
  • Marking this as correct as it also mentions the reversal of object order, thank you for your help! – fokicheva Dec 05 '22 at 17:01
  • For `objA = { lastName: null }` or `objA = { lastName: "" }` the default in `const { lastName = objB.lastName } = objA;` will not kick in. The result would be `null` or `""` respectively. – VLAZ Dec 05 '22 at 17:05
  • 1
    @VLAZ, good point. Same is true when spreading. Only the old-school code handles falsy values. – gilly3 Dec 05 '22 at 17:15
  • @gilly3 Yep. I agree with you - no real need for fancy solutions. The original is clear, fast, and correct. – VLAZ Dec 05 '22 at 17:18