Understanding Javascript Optional Chaining.

Understanding Javascript's Optional Chaining

As programmers, a major part of our work revolves around data. It is either we are generating it (assigning variables), manipulating it (looping, conditional statement, etc), saving it, or updating it.

Data is at the core of our jobs, and with the several operations we carry out on and with it, we build systems that affect our lives in many ways. Some ways are rather insignificant, others can have serious, (and sometimes, fatal) consequences for us.

The state of data is (mostly) unpredictable

Ironically, at several points in the system, the status of the data we work with is unpredictable. Either because the source of the data is outside of the system, or because other parts of our system might have had some manipulations on the data that have unpredictable outcomes.

At times like this, making assumptions about the state of the data might lead to consequences that are injurious to the system, and sometimes to our users.

An example of such a possibility is trying to access a property on an object when there is a possibility that the object itself does not exist. For example:

const person = null;

const name = person.name;

(Look beyond the fact that we are setting the person object to null for the moment, as any process in the system could have been responsible for doing so. Moreover, optional chaining shines when we are accessing values deep down in the hierarchy).

Attempting to run this in Google Chrome DevTools will display the following error:

Uncaught TypeError: Cannot read property 'name' of null
    at <anonymous>:1:8

But we could overcome this by rewriting the code above as:

const person = null;
let name;

if(person) {
   name = person.name
}

which is not bad considering this example.

But as is often the case, real-world data has a lot of nesting, and sometimes the data point we are interested in is several layers below the outermost variable. Consider the example below:

const dele = {
   name: {
       first: 'Dele',
       last: 'Somefun'
   },
   dob: {
       year: 1997,
       month: 5,
       day: 25
   },
   hobbies: {
      games: {
         outdoor: ['football', 'basketball', 'Tennis'],
         indoor: ['reading', 'chess']
      },
      skills: ['painting', 'reading'],
   }
};

Agreed. That object looks like it was deliberately crafted for mischievous reasons. But the honest truth is, it is not impossible to meet such structure in production systems.

Now, let's say Dele has cooking as one of his hobbies, and the structure of cooking is supposed to look like this:

const cooking = {
   soup: ['vegetable', 'stew'],
   solid: ['pounded yam']
};

But in the object above, cooking is omitted, as such, it will be undefined. Any attempt to access the soup property of cooking will throw an error:

dele.hobbies.cooking.soup
Uncaught TypeError: Cannot read property 'soup' of undefined
    at <anonymous>:1:22

So how do we solve this? We could simply do an if statement:

if(dele.hobbies.cooking) {
   ...
}

This will work. But, only because we are sure that the hobbies property on dele exists. And that dele itself is not an empty object, or worse a null reference, assumptions we may not be guaranteed in a production environment. So, what if we are not sure that any of these variables/properties exist prior to needing them?

Before optional chaining, we would do something like this:

const deleFavouriteSoupHobby = dele && dele.hobbies && dele.hobbies.cooking && dele.hobbies.cooking.soup && dele.hobbies.cooking.soup[0];

I am quite sure you agree with me. That's terrible code.

Optional Chaining to the rescue!

Optional chaining to the rescue. With optional chaining, all we need to do is add ? before the property accessor. Like this:

const deleFavouriteSolidFoodHobby = dele?.hobbies?.cooking?.solid?.[0];

And just like that, we escape the runtime reference error and also avoid having to write hideous code. What the ? before the . does is to check if the property exists. If it does, then, the program tries to access the next property. If it does not, it simply degrades gracefully and assigns undefined to the variable being created.

How do we use optional chaining?

1. With object's property.

const lastName = dele.name?.last;

2. With array's indexes.

const firstSolid = cooking.solid?.[0];

3. With methods of objects.

const currentLocation = dele.calculateLocation?.();

Suppose we expect dele to have a calculateLocation method that should return his current location, but we are not sure the method exists, we could use optional chaining while invoking it, and if it doesn't exist, our new variable will be undefined.

If calculateLocation is a property of another type that is not a method, an error will still be thrown.

When is optional chaining not permitted?

An important rule is, you cannot use optional chaining on the left-hand side of an assignment expression:

dele?.cooking = {}
Uncaught SyntaxError: Invalid left-hand side in assignment

There's a possibility that this will be available in future releases of Javascript, but for now, the language simply doesn't permit it.

Conclusion

Optional chaining helps us access properties that we cannot guarantee will be there at runtime, as though they are actually there. Because we know that our code will handle the absence of such property gracefully by simply making them undefined, helping us avoid unnecessary crashing of our application, and possibly potential losses of time, finance, or worse.

If you have any questions, you can leave them in the comments section below. Thanks for reading.