In our starter files, open the index.html page from this lessons folder:
06.Loops-and-conditionals > 10.Optional-chaining
We will now look at something called optional chaining, which was introduced to JavaScript in ES2020.
As we know already, we often need to access properties inside of an object. And we can go deeper by accessing objects inside of objects too. But what happens when these properties in the chain are not there?
We looked at some examples of chaining in the events section. We accessed the event object, then went deeper into properties such as the target
, the parentNode
, the children
and then into the innerText
value:
function addToFavourites(e) {
alert(`${e.target.parentNode.children[2].innerText}`);
}
If at any point in this chain a property does not exist, we would get an error.
This is not a bad thing though, we should get errors if we do something wrong, but what about if we know the property may not always be there?
In the starter project we have an example:
let currentUser = { name: "Chris", role: "admin" };
if (currentUser.role === "admin") {
console.log(true);
} else {
console.log(false);
}
We have a logged in user, and we check the user’s role
property. Users are not always going to be logged in. If they are logged out, currentUser
may be set to null
:
let currentUser = null;
This would give us a console error similar to this:
Uncaught TypeError: Cannot read properties of null (reading 'role')
This happens because currentUser
is no longer an object which contains the role
property, causing the error. This is where optional chaining comes in.
We insert a question mark to declare the property may or may not be there:
let currentUser = null;
if (currentUser?.role === 'admin') {
console.log(true);
} else {
console.log(false);
}
This now removes the error, and the else section will run. Since currentUser
is null
, JavaScript will now not need to go any further and check the rest of the chain.
This now skips the error and instead returns a value of undefined
, which we can check with a console log:
let currentUser = null;
if (currentUser?.role === "admin") {
console.log(true);
} else {
console.log(false); // false
console.log(currentUser?.role); // undefined
}
This is useful if we know that a property may or may not be there. It is common for objects like this to grow over time, we don’t always know how our app may develop in the future, we may need to add extra features or collect more information.
For example, in the future we may need more information about the users sign up source, and could add a nested object inside like this:
let user = {
name: 'Chris',
role: 'admin',
signUpData: {
date: '',
confirmed: true
},
};
Then again down the line, we may need to go deeper, and collect further detail about the sign-up source in an object too:
let user = {
name: 'Chris',
role: 'admin',
signUpData: {
date: '',
source: {
url: 'https://...',
browser: 'chrome',
},
confirmed: true,
},
};
Now we need a longer chain to access this information. To see this, remove the if else
section, and log to the console:
console.log(user.signUpData.source.browser);
This will log the browser, but what about our earlier users who signed up before we added this browser information?
Some users may not have this source
object. To simulate this, comment out this section as below, and again try in the console:
let user = {
name: "Chris",
role: "admin",
signUpData: {
date: "",
// source: {
// url: 'https://...',
// browser: 'chrome',
// },
confirmed: true,
},
};
console.log(user.signUpData.source.browser);
Uncaught TypeError: Cannot read properties of undefined (reading 'browser')
As you would expect, we get an error since we no longer have a browser value. Optional chaining can again help us out by allowing the source
to fail silently:
console.log(user.signUpData.source?.browser);
This way we now have a value returned of undefined
. So overall errors are good to let us know when we do something wrong. But in cases like this when we know a property may or may not be there, optional chaining can help us out by failing silently, and allowing us to handle the outcome ourselves.