Optional Chaining And Nullish Coalescing In JavascriptSail through the sea of complex nested objects in a flash
  Tagged under:  # Javascript
  Date:          
  Time to read:  9 min read
  Author:  Vigneshwar Balaji

Optional Chaining And Nullish Coalescing In Javascript

Introduction:

The optional chaining and nullish coalescing are the two features introduced in ES2020. These two operators simplify working with nested properties and methods of an object. These operators allow us to develop a cleaner and more elegant way of navigating through the complex nested object's properties without triggering any typeerror when encountering null or undefined values.

Objects in Javascript:

Objects in Javascript are collections of properties and methods. These properties and methods are usually accessed through "." notation. Consider the following example

let country = {
    name: "France",
    capital: "Paris",
    continent: "Europe"
}

console.log(country.name)//Output: France

The above example shows how objects are accessed in Javascript. Here the name property of the country object is accessed through "." notation and it is printed using a console.log

The problem:

In the above country object as we had defined the properties manually it is easy for us to print them without checking them but in the case of a practical application where the data is fetched from a database, it is more likely for us to end up with null or undefined values. For example, take a country like Nauru in Oceania it has no capital in such a case we are more likely to end up with a null or undefined value.

let country = {
    name: "Nauru",
    continent: "Oceania"
}

console.log(country.capital)//Output: undefined

In the same way, if we try to access a deeply nested object that does not exist we are more likely to end with a type error which would stop the program execution abruptly. For example:

let country = {
    name: "Tuvalu",
    capital: "Funafuti Atoll"
}

console.log(country.continent) // undefined
console.log(country.peopleDetails.population)// ERROR

The Legacy Solution:

The traditional way to avoid the above-explained error is to use a "logical operator(&&)". It involves multiple checks to check whether the property or method exists or not before diving deeper into the object. For example:

let country = {
    name: "Mongolia",
    capital: "Ulaanbaatar",
    continent: "Asia",
    peopleDetails:{
        population: "34,00,000"
    }
}
let population = "";
if (country && country.peopleDetails && country.peopleDetails.population){
    population = country.peopleDetails.population
}else{
    population = "not calculated"
}

console.log(population)//Output: 34,00,000

The above-displayed code checks whether the country object exists or not if it exists check for "peopleDetails" property if it also exists then check if "population" property exists if it all passes we would be assuming the country object's population property to population variable. If the condition fails then the population variable will be set to "not calculated". In the above code, there won't be any "typeerror" as the expression immediately short circuits once it encounters an undefined or null value. In such a situation of short-circuiting the evaluation of a condition stops immediately and an undefined value is returned.

Note: The above process of evaluating the condition using a logical operator is called "Short-circuiting". Short-circuiting refers to the process where the evaluation of a condition stops immediately after encountering a null or undefined value.

In the above example the typeerror was avoided by the use of (&&) logical operator short-circuiting. Even though the error was avoided it requires more code and it spoils the readability of the code. To cap it all if the property that we require is nested deeper the above legacy solution gives a more complex look. To avoid the above issue we can employ "optional chaining" as a way of writing more cleaner code.

The new ES2020 solutions:

Optional Chaining:

The "Optional Chaining" operator is denoted by "?.". Comparing the logical operator (&&) the optional chaining operator makes the code cleaner and simpler. Let us assume that we want to access a deeply nested property of an object if any property in-between that property is either undefined or null the code execution will immediately stop and will return undefined as a value.

For example

const getPeopleDetails = () => {
    const peopleDetails = {
        population: "1,425,178,782",
        dialingCode: "+86"
    }
	return peopleDetails;
}

let country = {
    name: "China",
    capital: "Beijing",
    continent: "Asia",
    getPeopleDetails : () => {
        return {
            population: "1,425,178,782",
            dialingCode: "+86"
        }
    }
}

const dialingCodeOfTheCountry = country?.getPeopleDetails?.().dialingCode || "No dialing code"
console.log("Optional chain on country object : ", dialingCodeOfTheCountry) //Output: +86

The above code shows how to access the deeply nested property(i.e. dialingCode) of the country object without any issues using optional chaining. The above code short-circuits immediately if it encounters any null or undefined values and optional chaining makes the code look cleaner than chaining the object property with logical operators.

In the above code, the optional chaining operator is used to call the getPeopleDetails method of the country object and access its dialingCode property. If any of the property in-between in the chain is null or undefined, the expression would have short-circuited and it would have returned "No dialing code".

Optional Chaining on dynamic objects:

In the below example, we will declare an array of country objects and we will try to access the population property of those objects with a traditional logical operator short-circuiting approach.

Legacy Approach:

let countries = [
    {
        name: "China",
        capital: "Beijing",
        continent: "Asia",
        peopleDetails: {
            population: "1,425,178,782",
            dialingCode: "+86"
        }
    },
    {
        name: "England",
        capital: "London",
        continent: "Europe"
    },
    {
        name: "Brazil",
        capital: "Brasilia",
        continent: "SouthAmerica",
        peopleDetails: {
            population: "217,637,297",
            dialingCode: "+55"
        }
    },
    {
        name: "Seychelles",
        capital: "Victoria",
        continent: "Africa"
    }
];

let populationDetails = countries.map(country => {
  if (country && country.peopleDetails && country.peopleDetails.population) {
    return country.peopleDetails.population;
  } else {
    return "no details on population";
  }
})

console.log("get population details using optional chaining:", populationDetails);
/*
Output:

get population details using optional chaining: [
  '1,425,178,782',
  'no details on population',
  '217,637,297',
  'no details on population'
]*/

Optional Chaining approach:

In the below example, we will look into the way of using optional chaining in an array of objects. Let us assume we have an array of country objects and we are going to access them using optional chaining.


let countries = [
    {
        name: "China",
        capital: "Beijing",
        continent: "Asia",
        peopleDetails: {
            population: "1,425,178,782",
            dialingCode: "+86"
        }
    },
    {
        name: "England",
        capital: "London",
        continent: "Europe"
    },
    {
        name: "Brazil",
        capital: "Brasilia",
        continent: "SouthAmerica",
        peopleDetails: {
            population: "217,637,297",
            dialingCode: "+55"
        }
    },
    {
        name: "Seychelles",
        capital: "Victoria",
        continent: "Africa"
    }
];

const populationDetails = countries.map(country => country?.peopleDetails?.population || "no details on population");

console.log("get population details using optional chaining:", populationDetails);
/*
Output:

get population details using optional chaining: [
  '1,425,178,782',
  'no details on population',
  '217,637,297',
  'no details on population'
]*/

In the above examples, we are iterating through an array of country objects using the map function and we are getting the population property of those country objects and we are trying to print it. In some of the country objects, there are no population details as a result if there is no population-related information means we are simply printing "no details on population". Here if any of those intermediate properties of country objects don't exist means the execution of the condition immediately terminates or short-circuits and "no details on population" is printed.

From the comparisons we have seen so far, it is visible that optional chaining makes the code cleaner and reduces the complexity of the code. Hence it is better to use "Optional Chaining" instead of the logical (&&) operator in this kind of situations.

Nullish Coalescing:

The nullish coalescing operator is denoted by "??.". The nullish coalescing is similar to the logical OR operator except it treats null and undefined as falsy values and everything as truthy values. In some cases, we might want to use default values if a variable is either null or undefined in such a case nullish coalescing is used.

Nullish Coalescing usually checks if the value on the left side is null or undefined if it is, it returns the value on the right side otherwise it returns the value on the left side.

Use cases of Nullish coalescing:

The nullish coalescing operator is created as an improved alternative to the logical OR(||) operator. The OR operator was created to provide a fallback value when the left-hand expression is falsy but there are times at which the OR operator won't function as intended. For example, there are times at which developers want to return values to the values that are considered falsy such as "0" and an empty string(""). In such a case the OR operator will prevent us from returning any falsy values.

Using Logical OR Operator

let player = {
    name: "John Doe",
    age: 23,
    goals: 0,
    position: "defense"
}

let user = {
    name: "",
    company: "Newtonian Dynamics"
}

console.log(player.goals || "Amateur") //Output: Amateur
console.log(user.name || "Guest") // Output: Guest

In the above code sample even though the goals property of the player object is not null or undefined the console.log is printing the default value instead of printing the actual value of the goals property. This is because the OR operator considers "0" a falsy value as a result it prints "Amateur" as an output. Similarly for the "user" object the empty string of name property is not printed instead "Guest" is printed, as an empty string is a falsy value. This kind of situation can be handled by the "Nullish coalescing" operator.

Using Nullish Coalescing operator

let player = {
    name: "John Doe",
    age: 23,
    goals: 0,
    position: "defense"
}

let user = {
    name: "",
    company: "Newtonian Dynamics"
}

console.log(player.goals ?? "Amateur") //Output: 0
console.log(user.name ?? "Guest") // Output: ""

Here the nullish coalescing operator won't print a default value until or unless there is a null or undefined value in those desired properties of the object as a result it printed "0" and ""(empty string)

Conclusion:

This article explored the new features introduced in ES2020 namely "Optional Chaining" and "Nullish Coalescing" and their use cases. We also went through some code samples on the declaration of objects and the problems that are caused when accessing the nested objects and resolving those issues in the legacy way using the logical (&&) operator. Then we went on to explore how the legacy approach simply leads to more complex code and the ways of avoiding it using "optional chaining". After that, we went on to explore "Nullish coalescing" operator and its use cases.

Thanks for reading.