Optional Chaining

Open the project folder

In our starter files, open the index.html page from this lessons folder:

06.Loops-and-conditionals > 10.Optional-chaining

Accessing nested properties

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?

Starter project example

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.

Using optional chaining

optional chaining link

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.