How does Optional Chaining work in Javascript?
Tags: javascript, webdev, tutorial, typescript
Published: 2022-07-16 13:10:22

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:

```javascript
let article = {};
let url;
if(article.relatedArticle !== undefined) {
    url = article.relatedArticle.url;
}
```

Or we may be tempted to try something like this instead:
```javascript
let article = {}
let url = article.relatedArticle ? article.relatedArticle : undefined;
```

This ultimately avoids the following error, should `relatedArticle` be undefined:
```
Cannot read properties of undefined (reading 'url')
```

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:
```javascript
let article = {};
let rootDomain;
if(article.relatedArticle !== undefined && article.relatedArticle.url !== undefined) {
    rootDomain = article.relatedArticle.url.rootDomain;
}
```

**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:
```javascript
let article = {};
let rootDomain = article.relatedArticle?.url?.rootDomain;
```

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](https://fjolt.com/article/javascript-nullish-coalescing). Below, if the `rootDomain` property is found to be undefined, it will instead return `https://google.com/`:
```javascript
let article = {};
let rootDomain = article.relatedArticle?.url?.rootDomain ?? 'https://google.com/';
```

## 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:
```javascript
let article = {};
let rootDomain = article.relatedArticle?.url?.rootDomain;
```

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:
```javascript
let article = {};
let rootDomain = article.relatedArticle?.url?.rootDomain?;
// Will throw an error like `Unexpected token ';'`
```

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`:
```javascript
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;
```

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.
```javascript
let myUser = {
    name: "John Doe",
    age: 155
}

let getAddress = myUser.addresses?.[0]?.first
```

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:
```javascript
let myObject = [
    {
        id: 15,
        adder: (x) => {
            return x + 10;
        }
    },
    {
        id: 145,
    }
]

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

```

**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: 
```javascript
let runFunction = item.adder?.(10) || 10;
```
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.