66

When I try to merge two objects using the spread operator conditionally, it works when the condition is true or false:

let condition = false;
let obj1 = { key1: 'value1'}
let obj2 = {
  key2: 'value2',
  ...(condition && obj1),
};

// obj2 = {key2: 'value2'};

When I try to use the same logic with Arrays, it only works when the condition is true:

let condition = true;
let arr1 = ['value1'];
let arr2 = ['value2', ...(condition && arr1)];

// arr2 = ['value2', 'value1']

If the condition is false an error is thrown:

let condition = false;
let arr1 = ['value1'];
let arr2 = ['value2', ...(condition && arr1)];

// Error

Why is the behaviour different between Array and Object?

5
  • 1
    Not cleared. What do you want to achieve here? Can you please paste your (condition && arr) as well? Commented May 7, 2019 at 11:34
  • FYI, { ...null } and { ...undefined } won't throw error either. So, we can forgo that check as well if we are unsure if an object has value or not Commented May 7, 2019 at 12:13
  • 2
    FWIW if you wanted to spread booleans, you could do something like Boolean.prototype[Symbol.iterator] = function* () {} (obviously don't do this in production code, it's a mere curio) Commented May 7, 2019 at 14:00
  • Is what you really want to achieve condition ? [...arr1, ...arr2] : arr1 or is this just a question on how the language works? Commented May 7, 2019 at 14:20
  • 4
    @JollyJoker it's just a question on how the language works. Thanks Commented May 7, 2019 at 14:34

5 Answers 5

97

When you spread into an array, you call the Symbol.iterator method on the object. && evaluates to the first falsey value (or the last truthy value, if all are truthy), so

let arr2 = ['value2', ...(condition && arr)];

results in

let arr2 = ['value2', ...(false)];

But false does not have a Symbol.iterator method.

You could use the conditional operator instead, and spread an empty array if the condition is false:

let condition = false;
let arr1 = ['value1'];
let arr2 = ['value2', ...(condition ? arr1 : [])];
console.log(arr2);

(This works because the empty array does have the Symbol.iterator method)

Object spread is completely different: it copies own enumerable properties from a provided object onto a new object. false does not have any own enumerable properties, so nothing gets copied.

Sign up to request clarification or add additional context in comments.

Comments

25

false is not spreadable.

You need a spreadable object (the one where Symbol.iterator is implemented) which returns nothing, if spreaded.

You could use an empty array as default value. This works even if arr is falsy.

let condition = false;
let arr1 = ['value1'];
let arr2 = ['value2', ...condition && arr || []];

console.log(arr2);

5 Comments

would add parenthesis to make it clearer ((condition && arr) || [])
This could really use the ?: operator instead of a chained ||.
@deceze, it really depends on the use case.
I think this is a use case where ?: is a lot more appropriate. ;)
Couple of points: Should have used const arr1 ...., const arr2 ... instead of var. Can be simplified to: const arr2 = ['value2', ...(condiition ? ['value1'] : [])];
13

This is a specification difference between the spread syntax for object literals and for array literals.

MDN briefly mentions it here -- I highlight:

Spread syntax (other than in the case of spread properties) can be applied only to iterable objects

The difference comes from the EcmaScript 2018 specification:

  • Concerning object spread syntax, see 12.2.6.8 Runtime Semantics: PropertyDefinitionEvaluation:

    • It calls CopyDataProperties(object, fromValue, excludedNames) where the fromValue is wrapped to an object with ToObject, and therefore becomes iterable, even if fromValue is a primitive value like false. Therefore {...false} is valid EcmaScript.
  • Concerning array spread syntax, see 12.2.5.2 Runtime Semantics: ArrayAccumulation:

    • It merely calls GetValue(spreadRef) which does not do the above mentioned wrapping. And so the subsequent call to GetIterator will trigger an error on a primitive value, as it is not iterable. Therefore [...false] is invalid EcmaScript.

Comments

2

Example with objects:

 const lessonMenuItems = [
  ...(true
    ? [
        {
          value: 'post',
        },
      ]
    : []),
  {
    value: 'assign',
  },
]

Result:

lessonMenuItems = [
  {
    value: 'post',
  },
  {
    value: 'assign',
  },
]

Comments

0

You can use a ternary operator (? :) and put an empty array as the false

let condition = false;
let arr1 = ['value1'];
let arr2 = ['value2', ...(condition ? arr1 : [])];
console.log(arr2);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.