Optional Chaining and Nullish Coalescing Operators in JavaScript - What, Why and How

It’s 2020 and our beloved JavaScript is getting some new features. They are-

  • - String.prototype.matchAll
  • - import()
  • - BigInt
  • - Promise.allSettled
  • - globalThis
  • - for-in mechanics
  • - Optional Chaining
  • - Nullish coalescing Operator

In the previous post, I’ve written about the dynamic import. In this post, we’ll have a look at what is optional chaining and nullish coalescing operator, why should we use it, and how to use it.

I’m using Chrome 80 to try the below examples.

Optional Chaining

Have you ever experienced the need to access a property sitting deep in the nested object and to check whether the intermediary node exists? If so, then you must have checked those intermediary nodes through & operator. Optional chaining operator, introduced in the ES2020, gives a concise syntax to perform the checking.

To understand the problem, let’s look at an example. Suppose, we have an application that filters out anyone whose office location is not in Dhaka.

/*
  An object holding the information for the persons. A person having
  "student" profession does not have office property.
*/
const personInfos = [
  {
    firstName: 'Kamal',
    lastName: 'Sharif',
    profession: 'Senior Software Engineer',
    office: {
      name: 'Field Nation',
      location: 'Dhaka',
    },
    fullName() {
      return `${this.firstName} ${this.lastName}`;
    },
  },

  {
    firstName: 'Karl',
    lastName: 'Max',
    profession: 'Senior Software Engineer',
    office: {
      name: 'Field Nation',
      location: 'Minnesota',
    },
  },

  {
    firstName: 'John',
    lastName: 'Smith',
    profession: {
      title: 'student', // students do not have office!
    },
  },
];

/*
  We want to filter out the persons whose office location is not in Dhaka
*/
function personsWithOfficesInDhaka(persons) {
  return persons.filter(person => person.office.location === 'Dhaka');
}

personsWithOfficesInDhaka(personInfos);

If you run the above code, you will get the error Cannot read property ‘location’ of undefined. Because not every element has the office property. So, there’s no location to access.

There’s a popular workaround to this issue. We will check if there’s an office property and only then try to compare the location. Let’s apply this.

function personsWithOfficesInDhaka(persons) {
  // compare location if office is there
  return persons.filter(
    person => person.office && person.office.location === 'Dhaka'
  );
}

In our simple example. we got away with the single &&. But in reality, we regularly have to deal with deeply nested objects. The property checking might require multiple AND operators. It hurts code readability.

Using an optional chaining operator increases code readability and reduces typing by eliminating the need to use multiple AND operator. For example, we can tweak the implementation in the following way.

function personsWithOfficesInDhaka(persons) {
  // compare location if office is there
  return persons.filter(person => person.office?.location === 'Dhaka');
}

This looks clean, right? You might be wondering about the ?. syntax. We’ll look into it in the next section.

So, how does this ?. work? If the left-hand side of the ?. operator evaluates to undefined or null, the entire expression evaluates to undefined. Otherwise the normal operation of accessing property happens.

In our example, when the array element does not have the property office, the person.office?.location evaluates to undefined which does not equal to the value Dhaka. Hence, it gets filtered out.

The bracket notation works too. For example, person.office?.['location'] is just fine.

If the left-hand side of the operator evaluates to null or undefined, then the right-hand side will not be evaluated. It will also not evaluate the entire chain. For example, if we have person.office?.location.coordinates.longitude, part from the location will not be evaluated. This is called short-circuiting.

What if there is a location, but not the coordinates? Then again we’ll get the TypeError. We need to use the optional chaining operator for the properties we think could be null/undefined. So, person.office?.location?.coordinates.longitude will do the job. This is called long short-circuiting.

It is possible to limit the scope of optional chaining by using parentheses i.e. (person.office?.location?.coordinates).longitude. This is called grouping. But I’ve not found any use case so far because if the expression inside the parentheses evaluates to undefined, then we will get the TypeError.

Optional chaining operator works for the methods too. This allows us to invoke function and supply arguments if it is there. Otherwise, we get undefined. I’m going to use the same example to demonstrate how ?. with method works.

We don’t have the fullName method for every person. Suppose, we want to get the full name for the senior software engineers.

function fullNameOfSeniorSWEs(persons) {
  return persons
    .filter(person => person.profession === 'Senior Software Engineer')
    .map(person => person.fullName?.()); // not every element have fullName method!
}

fullNameOfSeniorSWEs(personInfos); // ['Kamal Sharif', undefined]

If the method takes arguments, you can pass it like person.fullName?.(a, b).

At the time of this writing, browser support is not very good. It is not supported in the current Node v13.10.1. Optional chaining operator is available in TypeScript from v3.7 and in Babel through this plugin.

Nullish Coalescing

More often we intend to set the default values for object properties or variables. Nullish coalescing operator is here to help us in this aspect.

Let’s look at another example.

function setHeader(header) {
  return header || 'default heading';
}

We have a function that sets a header. We want to return the default value if the header is not there. If we don’t pass the argument it will show the default text. The common art to display default value is || operator. But there is a problem. It will show the default value for any falsy values.

setHeader(''); // 'default heading'

It is desirable that if an empty string is passed we want to display that. For other falsy values, we are fine with the default. Obviously. there is a verbose workaround to this. But the nullish coalescing operator provides an easier way to achieve this.

function setHeader(header) {
  return header ?? 'default heading';
}

setHeader(''); // ''
setHeader(); // 'default heading'
setHeader(null); // 'default heading'
setHeader('Random heading'); // 'Random heading'

The ?? is called the nullish coalescing operator. If the left-hand side of the operator evaluates to null or undefined, it returns the right-hand side. Using this operator, now it is possible to show the empty string. Pretty neat!

At the time of this writing, browser support is not very good. It is not supported in the current Node v13.10.1. Nullish coalescing operator is available in TypeScript from v3.7 and in Babel through this plugin.

Let’s go back to the personInfos example. We wrote a function previously that returns the full name of the senior software engineers.

function fullNameOfSeniorSWEs(persons) {
  return persons
    .filter(person => person.profession === 'Senior Software Engineer')
    .map(person => person.fullName?.()); // not every element have fullName method!
}

fullNameOfSeniorSWEs(personInfos); // ['Kamal Sharif', undefined]

Just for example, instead of undefined we want a default text. We can combine nullish coalescing with the optional chaining to achieve that.

function fullNameOfSeniorSWEs(persons) {
  return persons
    .filter(person => person.profession === 'Senior Software Engineer')
    .map(person => person.fullName?.() ?? 'no name'); // displays text if fullName method is absent
}

fullNameOfSeniorSWEs(personInfos); // ['Kamal Sharif', 'no name']

That’s it! All the best using optional chaining and nullish coalescing operators!

Share on

Get the latest post in your inbox!

Kamal Sharif
Hi, I'm Md. Kamal Sharif. I'm a software engineer based in Dhaka, Bangladesh.

Follow Blog

Posts you might like

Advance usage of Array.prototype.reduce() in JavaScriptLife in the time of coronavirus in a developing nationExploring ES2020 features - Dynamic Import