Introduction

This article I can start by saying, "Finally". In my opinion, the following functionalities are a very useful addition to the JavaScript language.

Optional chaining enables us to handle tree-like structures without explicitly checking if the intermediate nodes exist, and nullish coalescing works great in combination with optional chaining and it's used to ensure the default value for an unexisting one.

At the moment of writing this, both of these proposals were at stage 3. You can check out the optional chaining here and nullish coalescing here.

Let's say we have an object:

const user = {
  name: 'John',
  surname: 'Doe',
  fullName() {
    return `${this.name} ${this.surname}`;
  }
};

The overall usage of the optional chaining is by one of the following ways:

user?.name; // static
user?.['name']; // dynamic
user.fullName?.(); // function call

By reviewing the code above, we can conclude it could be used with static and dynamic properties, as well as with functions. To show the real benefits, first, we need to understand how the same behavior is achieved without these new features.

The Old Ways Comparison and Alternatives

Before this proposal, we had a couple of ways to achieve the desired behavior. We needed to manually check for the property existence by using the && operator, and if we wanted to ensure the default value (instead of undefined), || operator was called to the rescue.

Also, a lot of external libraries, like lodash, have their own workarounds.

Static Property

const user = {
  name: 'John',
  surname: 'Doe',
  fullName() {
    return `${this.name} ${this.surname}`;
  }
};

// new
user?.name;

// old
(user !== undefined && user !== null) ? user.name : undefined;

Dynamic Property

const user = {
  name: 'John',
  surname: 'Doe',
  fullName() {
    return `${this.name} ${this.surname}`;
  }
};

// new
user?['name'];

// old
(user !== undefined && user !== null) ? user['name'] : undefined;

Function Call

const user = {
  name: 'John',
  surname: 'Doe',
  fullName() {
    return `${this.name} ${this.surname}`;
  }
};

// new
user.fullName?.();

// old
(user !== undefined && user !== null 
  && Object.prototype.toString.call(user.fullName) === '[object Function]') ? user.fullName() : undefined;

In every example, the difference between the old and the new way of doing things is that without optional chaining, we need to check if the object exists, and only if it does, we can access its property or function because otherwise, we would get an error.

Note that optional chaining will always return undefined if the expression fails

Bonus - DOM Support

Optional chaining does support DOM and its methods, meaning that it's possible to do something like this:

const value = document.querySelector('input#user-name')?.value;

Default Values and Nullish Coalescing

As mentioned earlier, the nullish coalescing operator allows us to use the default value instead of undefined. Before this proposal, to achieve this behavior we used || operator. You might wonder, why should I use ?? instead of ||? It's the same number of characters, my code will not be shorter...

The problem with || operator is the evaluation process. Just think of what values can evaluate as true or false, and you will have a possible unintended output, I'm sure.

const user = {
  name: 'John',
  surname: 'Doe',
  fullName() {
    return `${this.name} ${this.surname}`;
  }
};

// old
const name = user && user.name || 'Jim';

// new
const name = user?.name ?? 'Jim';

Example

Let's back up all above and wrap it into an example:

const user = {
  name: 'John',
  surname: 'Doe',
  address: {
    street: {
      name: 'My Street',
      number: 55,
    },
    state: 'USA'
  }
};

// to get the user name
const name = user && user.name || 'Jim';

// to get the user street
const street = user && user.address && user.address.street && user.address.street.name || 'Some Street';

// unexisting property
const province = user && user.address && user.address.street && user.address.province;

console.log(name); // outputs 'John'
console.log(street); // outputs 'My Street'
console.log(province); // outputs undefined

If we wanted to print out if the user is from the USA, the code would look something like this:

const isUserFromUSA = () => {
  if(user && user.address && user.address.street && user.address.state === 'USA') {
    console.log('Is from USA');
  }
}

isUserFromUSA() // outputs 'Is from USA'

With the combination of the two proposed operators, the code above looks a lot prettier, shorter, and more logical.

const user = {
  name: 'John',
  surname: 'Doe',
  address: {
    street: {
      name: 'My Street',
      number: 55,
    },
    state: 'USA'
  }
};

// to get the user name
const name = user?.name ?? 'Jim';

// to get the user street
const street = user?.address?.street?.name ?? 'Some Street';

// to check if the user is from the USA
const isUserFromUSA = () => {
  if(user?.address?.state === 'USA') {
    console.log('Is from USA');
  }
}

You can see how messy this can become without optional chaining and nullish coalescing, especially with an object having a more complex structure.

Conclusion

Optional chaining and nullish coalescing, in my opinion, brings us a lot of benefits in terms of code simplicity and logic. Today, you can use these new features with Babel and the plugins created for it. The links are below.

@babel/plugin-proposal-optional-chaining

@babel/plugin-proposal-nullish-coalescing-operator

If you liked the article, keep me juiced up for more by buying me a coffee and subscribe here or follow me on twitter to stay tuned.

Thank you for reading and see you in the next article.