Conditionally Add to an Object or Array in JavaScript

Cover image for this post: A screenshot of code showing how to conditionally add to an object or array in JavaScript, explained in the article.

In the course of my work, it's not uncommon that I need to conditionally add properties to objects, or (probably less commonly) values to arrays. Let's talk about how to do both. This is the piece of code I'll refer to:

const trueCondition = true;
const falseCondition = false;

const obj = {
  ...(trueCondition && { dogs: "woof" }),
  ...(falseCondition && { cats: "meow" }),
};

// { dogs: 'woof' }

const arr = [
  ...(trueCondition ? ["dog"] : []),
  ...(falseCondition ? ["cat"] : []),
];

// ['dog']

First, defining a few things.

If you're familiar with the && operator, the ternary operator, and spread syntax, skip ahead.

The logical && (AND) operator

&& is a logical operator. Logical operators are used to "reason" about Booleans. The && operator is one of three available in JavaScript (Not material here, but for completeness -- the two others are the || (OR) operator and ?? (nullish coalescing) operator.).

Usage

// expr1 && expr2

console.log(true && "hi");
// hi

console.log(`abc` && "123");
// 123

console.log({} && "empty but valid");
// empty but valid

console.log(false && "bye");
// false

If the first expression (on the left side) is truthy ("considered true when encountered in a Boolean context"), return the second expression (on the right side).

If the first expression is falsy ("considered false when encountered in a Boolean context"), return the first expression.

Short-circuit evaluation

The && expression is evaluated left to right. If the first expression is falsy, the full expression is short-circuit evaluated to the falsy expression (meaning the second expression is never evaluated). This is what lets us do things like safely access nested properties on an object:

const obj = {};

console.log(obj.first && obj.first.second);
// undefined

console.log(obj.first.second);
// TypeError: Cannot read property 'second' of undefined

The conditional (ternary) operator

The ternary operator can be thought of as a shortcut for the if statement. It's made of of three parts:

  • A condition followed by a question mark (?)
  • An expression to execute if the condition is truthy, followed by a colon (:)
  • an expression to execute if the condition is falsy
// condition ? exprIfTrue : exprIfFalse

An example. The two functions below accomplish the exact same thing using different syntax. The first uses if logic, and the second uses a ternary

function getWelcomeMessage(isLoggedIn) {
  if (isLoggedIn) {
    return "Welcome!";
  } else {
    return "Please log in.";
  }
}

console.log(getWelcomeMessage(true));
// Welcome!

console.log(getWelcomeMessage(false));
// Please log in.

function getWelcomeMessageTernary(isLoggedIn) {
  return isLoggedIn ? "Welcome!" : "Please log in.";
}

console.log(getWelcomeMessageTernary(true));
// Welcome!

console.log(getWelcomeMessageTernary(false));
// Please log in.

The spread operator (...)

Spread syntax can be used to expand an iterable (like an array expression), or expand object properties.

Spreading an iterable:

let myDogs = [`Riggins`, `Lyla`];
let parentsDogs = [`Ellie`, `Remi`];

const holidayDoghouse = [...myDogs, ...parentsDogs];
// [ 'Riggins', 'Lyla', 'Ellie', 'Remi' ]

Spreading object properties:

let existingAnimals = {
  dogs: 2,
  cats: 4,
  donkeys: 2,
  horses: 2,
};

let newAnimals = {
  goats: 2,
};

const allAnimals = {
  ...existingAnimals,
  ...newAnimals,
};
// { dogs: 2, cats: 4, donkeys: 2, horses: 2, goats: 2 }

It can be used on iterables like an array or a string. It expands an iterable to its individual elements

Conditionally add a property to an object

To conditionally add a property to an object, we can make use of the && operator.

const trueCondition = true;
const falseCondition = false;

const obj = {
  ...(trueCondition && { dogs: "woof" }),
  ...(falseCondition && { cats: "meow" }),
};

// { dogs: 'woof' }

In the example above, in the first property definition on obj, the first expression (trueCondition) is true/truthy, so the second expression is returned, and then spread into the object.

In the second property definition, the first expression (falseCondition) is false/falsy, and so the first expression is returned (and the second expression is never evaluated, because of short-circuiting). It may seem a little confusing to spread a falsy expression, but the result is that it is ignored:

const spreadFalsy = {
  ...false,
  ...null,
  ...undefined,
};

console.log(spreadFalsy);
// {}

You don't need parentheses in evaluating these expressions, but I prefer them, to make it clear that the spread operation applies to the result of the full expression.

const trueCondition = true;
const falseCondition = false;

const withParentheses = {
  ...(trueCondition && { dogs: "woof" }),
  ...(falseCondition && { cats: "meow" }),
};

// { dogs: 'woof' }

const withoutParentheses = {
  ...(trueCondition && { birds: "tweet" }),
  ...(falseCondition && { foxes: "???" }),
};

// { birds: 'tweet' }

Conditionally add a value to an array

Conditionally adding a value to an array looks a little different. Rather than using an && operator, we use a ternary operator.

Unlike the object spread example, if you attempt to spread on a falsy value in an array, you'll get a TypeError:

const falseCondition = false;

const arr = [...(falseCondition && ["cat"])];
// TypeError: boolean false is not iterable

Hence we need a ternary; Using a ternary, we can fall back to spreading an empty array. Then (assuming we've correctly provided two possible iterables) both possible returned expressions will be iterables:

const trueCondition = true;
const falseCondition = false;

const arr = [
  ...(trueCondition ? ["dog"] : []),
  ...(falseCondition ? ["cat"] : []),
];

// ['dog']

Cover image: By Amberley Romo