How does Optional Chaining work in Javascript?

Johnny Simpson - Jul 16 '22 - - Dev Community

Optional chaining is a feature in Javascript which lets us access child properties of an object, even if the parent object doesn't exist. It's used when properties are optional on an object, so that instead of returning an error, we still get a result from Javascript. Let's look at how it works.

Introduction to Optionality in Javascript

Anyone who has used Javascript for any length of time may have run into a pretty common issue where accessing a child property which is optional requires a lot of additional logic to test it exists, before we do anything.

As an example, imagine an article building piece of software, where the object article may have a child property called relatedArticle, and within that, we may have some basic information on a related article. Let's imagine we access a related article's URL by article.relatedArticle.url - however, not every article will have a related article. Traditionally in Javascript, assuming article is defined, we may have written something like this:

let article = {};
let url;
if(article.relatedArticle !== undefined) {
    url = article.relatedArticle.url;
}
Enter fullscreen mode Exit fullscreen mode

Or we may be tempted to try something like this instead:

let article = {}
let url = article.relatedArticle ? article.relatedArticle : undefined;
Enter fullscreen mode Exit fullscreen mode

This ultimately avoids the following error, should relatedArticle be undefined:

Cannot read properties of undefined (reading 'url')
Enter fullscreen mode Exit fullscreen mode

If we have multiple child levels, though, we can end up writing quite a long string of undefined statements. This can get quite tedious and out of control for very big objects. For example, below we try to access article.relatedArticle.url.rootDomain when we aren't really sure if these objects will be defined:

let article = {};
let rootDomain;
if(article.relatedArticle !== undefined && article.relatedArticle.url !== undefined) {
    rootDomain = article.relatedArticle.url.rootDomain;
}
Enter fullscreen mode Exit fullscreen mode

Optionality solves this problem of excessive undefined checks. If something is given an optional ?. tag, then if it exists, the value will be returned - otherwise it will not throw an error. In the above example, we don't even really need an if statement, anymore:

let article = {};
let rootDomain = article.relatedArticle?.url?.rootDomain;
Enter fullscreen mode Exit fullscreen mode

By adding ?., should relatedArticle, or url be undefined or null, it will simply pass onto the next property without throwing an error. We can even improve this by setting a default value using nullish coalescing. Below, if the rootDomain property is found to be undefined, it will instead return https://google.com/:

let article = {};
let rootDomain = article.relatedArticle?.url?.rootDomain ?? 'https://google.com/';
Enter fullscreen mode Exit fullscreen mode

How to use Optional Chaining in Javascript

In most examples, you can use optional chaining as described above, to avoid errors on truly optional properties within an object:

let article = {};
let rootDomain = article.relatedArticle?.url?.rootDomain;
Enter fullscreen mode Exit fullscreen mode

One caveat to this is that you cannot add a question mark to the end of an object. The following, will therefore, throw an error:

let article = {};
let rootDomain = article.relatedArticle?.url?.rootDomain?;
// Will throw an error like `Unexpected token ';'`
Enter fullscreen mode Exit fullscreen mode

Although this is the most common way to use optional chaining, there are other ways to implement it in your code. Let's explore those too, to see the full extent of how to use optional chaining in your code and projects.

When to not use optional chaining

Note: you should NOT use optional chaining to simply avoid errors. If you overuse optional chaining, you will silence errors and make it much more difficult to debug your code later. Instead, only use optional chaining for REAL optional properties!

Optional Chaining with Functions

You can use optional chaining with functions which you expect may return objects. It works exactly the same as before, only we add ?. after the function - myFunction(...arguments)?.x:

let myFunction = (x, y, z) => {
    if(z !== undefined) {
        return {
            x: x,
            y: y,
            z: z
        }
    }
}

let arguments = [ 1, 2, 3 ];

let getX = myFunction(...arguments)?.x;
console.log(getX); // Returns 1;
Enter fullscreen mode Exit fullscreen mode

It's good to know at this stage that by default, if a function has no return, it returns undefined - so this can be quite useful in situations where you are not sure a function will return anything at all.

Optional Chaining with Arrays

Optional chaining can also be used with arrays. For example - if you aren't sure if a property will be defined, but it contains an array. Below, a user can have an array of valid addresses, and we want to get the first line of their address, from their first address. Since it may not always exist, we can use optional chaining here to try and recover it.

let myUser = {
    name: "John Doe",
    age: 155
}

let getAddress = myUser.addresses?.[0]?.first
Enter fullscreen mode Exit fullscreen mode

In this case, addresses is not defined on myUser, so getAddress will simply return undefined. If we want to check a property which should be array, the notation is obj.prop?.[index], while if we want to check an array index which may not exist, but has child elements within, the notation is obj[index]?.prop.

Optional Chaining with Optional Functions

Sometimes, the return of an object property may be a function. In these cases, we may want to run this function, but since it isn't always defined, we only want it to fire if it's there. In this case, we can use the notation ?.() to run the function, should it exist. Consider the following example:

let myObject = [
    {
        id: 15,
        adder: (x) => {
            return x + 10;
        }
    },
    {
        id: 145,
    }
]

myObject.forEach((item) => {
    let runFunction = item.adder?.(10) || 10;
    console.log(runFunction);
});

Enter fullscreen mode Exit fullscreen mode

Above, we have an object which contains an array, where each object contains an id - id, and may also contain the function adder. In this example, we want to run this adder function if it exists, or default the value to 10 if the function is undefined - so we run the function:

let runFunction = item.adder?.(10) || 10;
Enter fullscreen mode Exit fullscreen mode

Here, if adder is defined, it runs, so we get x + 10 or 20, for the first element of the array, and simply 10 for the second, since adder does not exist.

Conclusion

Optional Chaining is incredibly useful, and widely supported (excluding Internet Explorer). It can be used with pretty much any combination of property, array or function, depending on return types. For example:

  • obj?.property
  • obj?.[property]
  • obj[index]?.property
  • obj?.(args)
  • func(args)?.property

Optional chaining is therefore a great way to simplify your code, and gives syntax and meaning to real optional properties within your objects.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .