Optional chaining is a feature in Javascript which lets us access the child properties of an object, even if the parent object doesn't exist. It's used when properties are optional on an object, so instead of returning an error, we still get a result from Javascript.
Let's look at how it works.
Anyone who has used Javascript for any length of time may have run into a pretty common issue where accessing a child property that 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;
}
Or we may be tempted to try something like this instead:
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:
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:
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. 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/';
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;
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 ';'`
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.
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!
You can use optional chaining with functions that 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;
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 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
In this case, addresses
is not defined on myUser
, so getAddress
will simply return undefined
. If we want to check a property that should be array, the notation is obj.prop?.[index]
, while if we want to check an array index that may not exist, but has child elements within, the notation is obj[index]?.prop
.
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);
});
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;
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.
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.
Also published here.