const - JS | lectureData Structures, Modern Operators & Strings

Optional Chaining (?.)

Data Structures, Modern Operators & Strings

In this lecture, let's learn about an even newer feature of objects and arrays called optional chaining. Let's say that we wanted to get the opening hours of our restaurant for Monday.

To check that we could do something like this:

script.js
console.log(restaurant.openingHours.mon); // Result ==> undefined

The property mon doesn't exist on the openingHours object. So if you try the above code snippet, you would get undefined. But let's pretend that we do not know whether this restaurant opens on Monday or not.

That could be the case. For example, if this data came from a real web service or API. And in the data received from that API, there could be multiple restaurants, and not all of them would open on Monday. And so we had no way of knowing if this particular restaurant would open on Monday or not.

Let's even go a little further because we want to know precisely the hour at which the restaurant opens on Monday. So let's see what we get.

script.js
console.log(restaurant.openingHours.mon.open);

OC - 2

You can see that we got an error because the result of restaurant.openingHours.mon was undefined. Hence, undefined.open is an error. To avoid this error, we would first have to check if restaurant.openingHours.mon actually exists. So we could do something like this:

script.js
if (restaurant.openingHours.mon) {
  console.log(restaurant.openingHours.mon.open);
}

Or we could also use a logical operator as we already learned. But, the above syntax is a bit more readable. Adding the if logic is not a big deal, but it makes our code more unreadable and messier. However, it is checking just for one property: Monday. But now imagine that opening hours would also be optional. In other words, maybe the restaurant object can also not have opening hours. So, we would have to check for both in this case.

We would have to do:

script.js
if (restaurant.openingHours && restaurant.openingHours.mon) {
  console.log(restaurant.openingHours.mon.open);
}

This can get out of hand quickly when we have deeply nested objects with lots of optional properties. Hence, therefore, ES2020 introduced an excellent solution for this, which is a feature called optional chaining.

If a specific property does not exist with optional chaining, undefined is returned immediately. Hence, that will prevent us from having that error we had earlier. Let me show you how it works.

console.log(restaurant.openingHours.mon?.open); // Result ==> undefined

Only if the property (Monday) that is before the question mark exists will the open property be read from there. If not, then immediately undefined will be returned. And exists here actually means the nullish concept that we already talked about before. i.e., a property exists if it's not null and not undefined. If it's zero or the empty string, then it still exists.

We can also have multiple optional chainings. Let's recreate the above if condition were we are testing for both opening hours and Monday. These are essentially the two optional properties that we do not know beforehand if they exist

// Without Optional Chaining
if (restaurant.openingHours && restaurant.openingHours.mon) {
  console.log(restaurant.openingHours.mon.open);
}

// With Optional Chaining
console.log(restaurant.openingHours?.mon?.open);

By adding a question mark after opening hours, we first check if the opening hours property exists. If it does not exist, well, then the Monday property will not even be read. This makes it easy to prevent all kinds of bugs that sometimes we might not even expect.

Now, let's see a scenario that can happen in the real world. For that, let's consider the below weekdays array from the previous lecture.

script.js
const weekdays = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];

I want to loop over this array and then log to the console, whether the restaurant is open or closed on each of these days.

script.js
for (const day of weekdays) {
  const open = restaurant.openingHours[day]?.open;
  console.log(`On ${day}, ==> ${open}`);
}

OC - 3

Our loop works great and we are able to log the opening hour of some days and getting undefined for others. Let's get rid of that by setting a default value.

script.js
for (const day of weekdays) {
  /**
   * Note that if you have zero as an opening hour you will have to use the nullish coelescing operator
   */
  const open = restaurant.openingHours[day]?.open || 'we are closed';
  console.log(`On ${day}, ==> ${open}`);
}

OC - 4

Now that you understand how it works, let's move to the next topic, optional chaining on methods. So optional chaining does indeed also work for calling methods. Essentially, we can check if a method exists before we call it.

script.js
console.log(restaurant.order?.(0, 2) ?? 'Method does not exist');

OC - 5

As you can see the method does exist and we get the expected result. If we now try to call a method that does not exist, here is what we get:

script.js
console.log(restaurant.orderRisotto?.(0, 2) ?? 'Method does not exist');

OC - 6

Just like before the optional chaining operator will check if orderRisotto exists. And if it doesn't, well, it will immediately return undefined. Hence, the nullish coalescing operator will immediately return second operand.

If we didn't use the optional chaining operator, then we would be trying to execute something that is not a function.

script.js
console.log(restaurant.orderRisotto(0, 2) ?? 'Method does not exist');

OC - 7

To finish this lecture, let's see how optional chaining works with arrays. We can use optional chaining on arrays to check if an array is empty.

script.js
const users = [
  {
    name: 'Mark',
    email: 'mark@constjs.dev',
  },
  {
    name: 'Joelle',
    email: 'joelle@constjs.dev',
  },
];

To get the name of the first element of this array, we can do this.

script.js
console.log(users[0]?.name ?? 'Users array empty!');

OC - 8

Again, the optional chaining operator tests if the value on the left does exist (users[0]). If it the users array was empty:

script.js
const users = [];
console.log(users[0]?.name ?? 'Users array empty!');

OC - 9

And without optional chaining, we would have to write something like this.

script.js
if (users.length > 0) {
  console.log(users[0].name);
} else {
  console.log('Users array empty!');
}

That's a lot more work to do. So, get used to this optional chaining operator, which is almost always used together with the nullish coalescing operator so that we can do something in case we don't get a result from the object or from the array that's on the left-hand side. With that being said, see you in the next lecture and happy coding!