javascriptvariable-assignmentobject-destructuringshort-circuit-evaluation

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


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.)


Solution

  • 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.