Mastering JavaScript: optional chaining and nullish coalescing
JavaScript is a popular programming language widely used in web development, and it empowers developers to build complex and interactive applications. As with any language, there are always new techniques and features that developers can learn and master to improve their skills and productivity.
Among these advanced JavaScript techniques are Optional Chaining and Nullish Coalescing. These features were introduced in ECMAScript 2020 (ES2020) and they provide a clear and concise way to access and manipulate values within objects
and arrays
.
They also help to minimize the amount of code required to handle null
and undefined
values, which can often lead to errors and bugs in JavaScript applications.
In this article, we will explore Optional Chaining and Nullish Coalescing in detail and learn how to use these techniques to write more efficient and reliable code in JavaScript. We will also look at some practical examples of how these features can be used in real-world scenarios.
Optional Chaining: what it is and how it works
Optional Chaining is a feature in JavaScript that allows developers to safely access the properties of an object without having to manually check if the object is null
or undefined
. This can be particularly useful when working with complex, nested objects.
The symbol used to denote Optional Chaining in JavaScript is the ?.
(question mark followed by a period).
Prior to Optional Chaining, developers often had to use the &&
(logical AND)operator to check if an object was null
or undefined
before accessing its properties. For example, consider the following code:
let obj = {
a: {
b: {
c: 'hello'
}
}
};
let value;
if (obj && obj.a && obj.a.b && obj.a.b.c) {
value = obj.a.b.c;
}
This code checks if obj
is defined and not null
, and if it is, it checks if obj.a
is defined and not null, and so on. If any of these checks fail, the value
variable will not be assigned a value.
With Optional Chaining, we can rewrite the above code as follows:
let value = obj?.a?.b?.c;
This syntax works by first checking if obj
is defined and not null
. If it is, it accesses the a
property of obj
. If obj.a
is defined and not null
, it accesses the b
property of obj.a
. If obj.a.b
is defined and not null
, it accesses the c
property of obj.a.b
. If any of these checks fail, the whole expression returns undefined
without throwing an error.
Optional Chaining can also be used to call functions. For example:
const person = {
name: 'Jane',
age: 30,
car: {
make: 'Toyota',
model: 'Camry',
getInfo: function() {
return `${this.make} ${this.model}`;
}
}
};
const carInfo = person?.car?.getInfo();
console.log(carInfo); // "Toyota Camry"
In this example, the person
object has a car
property, an object with a getInfo
method that returns a string with the car’s make and model. We can use the Optional Chaining operator (?.)
to access the car
property and then call the getInfo
method. If the person
object or car
property is undefined
, the expression will return undefined
, and no error will be thrown.
Optional Chaining is a handy feature that can help simplify and improve the readability of your code. It is supported in modern browsers and can also be used with transpilers such as Babel](https://babeljs.io/) to provide support in older browsers.
Understanding nullish coalescing in JavaScript
JavaScript has two types of null
values: null
and undefined
. null
represents an intentional absence of a value, while undefined
represents an uninitialized or missing value. Sometimes, you might want to use a default value if a variable is either null
or undefined
. This is where ‘Nullish Coalescing’ comes in.
Nullish Coalescing is a feature in JavaScript that allows you to check if a value is null
or undefined
; if it is, use a default value instead.
The symbol used to denote Nullish Coalescing in JavaScript is the ??
(double question mark).
This operator 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. It is very useful when you want to check if a variable has a value and provide a default value. It also helps eliminate the need for multiple lines of code that would have been used to check for null
or undefined
values, thereby making it more readable and clean.
Let’s take a look at a little example:
const name = null;
const defaultName = 'John Doe';
const displayName = name ?? defaultName;
console.log(displayName); // Output: "John Doe"
In this example, the displayName
variable is assigned the value of defaultName
because the name
variable is null
. If the name
variable had been undefined
, the displayName
variable would also have been assigned the value of defaultName
.
One thing to note is that the Nullish Coalescing operator only considers null
and undefined
as falsy values. All other values, including empty strings
and the number 0
, will be considered truthy.
Here’s an example to illustrate this:
const count = 0;
const defaultCount = 10;
const displayCount = count ?? defaultCount;
console.log(displayCount); // Output: 0
In this example, the displayCount
variable is assigned the value of count
, 0
, because the Nullish Coalescing operator(??
) only considers null
and undefined
as falsy values.
The displayCount
variable would have been assigned the value of defaultCount
if we had used the logical OR operator (||)
, this is because when using this operator, 0
is always considered as a falsy value.
The Nullish Coalescing operator also can be helpful in situations where you want to use a default value if a variable is null
or undefined
, but you don’t want to use the default value if the variable is an empty string
or the number 0
.
Here’s an example of how you can use the Nullish Coalescing operator with a function argument:
function getUserName(user) {
const name = user?.name ?? 'John Doe';
return name;
}
const user1 = { name: 'Jane Doe' };
console.log(getUserName(user1)); // Output: "Jane Doe"
const user2 = {};
console.log(getUserName(user2)); // Output: "John Doe"
const user3 = null;
console.log(getUserName(user3)); // Output: "John Doe"
const user4 = undefined;
console.log(getUserName(user4)); // Output: "John Doe"
In this example, the getUserName
function takes an object
as an argument
and returns the name property of the object
if it exists. If the name property is null
or undefined
, the Optional Chaining operator( ?.
) will return null
or undefined
, and the Nullish Coalescing operator (??
) will use the default value of ‘John Doe’.
The getUserName
function is then called with four different arguments:
user1
, which has a name property with a value of ‘Jane Doe’user2
, which is an emptyobject
and therefore does not have aname
propertyuser3
, which isnull
user4
, which isundefined
For user1
, the function returns the value of the name
property, ‘Jane Doe’.
For user2
, the function returns the default value of ‘John Doe’ because the name
property is not defined.
For user3
and user4
, the function also returns the default value of ‘John Doe’ because the user
argument is null
or undefined
.
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.
Use Cases for Optional Chaining and Nullish Coalescing
Let us dive deeply into a few practical applications of Optional Chaining and Nullish Coalescing in the real world and see how they can solve problems and improve code efficiency across various industries.
Use Case 1: Deal with missing values
Let’s say we have an e-commerce website that displays a user’s order history.
The feature would allow users to view their past orders, including information about the products they purchased, the date of purchase, and the order total.
The data for the orders are stored in a JavaScript object that looks something like this:
const orders = [
{
id: 1,
date: "2022-01-01",
items: [
{
product: {
name: "Shirt",
price: 30
},
quantity: 2
},
{
product: {
name: "Pants",
price: 50
},
quantity: 1
}
],
shipping: {
name: "John Smith",
address: {
street: "123 Main St",
city: "Springfield",
state: "Oregon",
zip: "97477"
}
}
},
{
id: 2,
date: "2022-02-01",
items: [
{
product: {
name: "Shoes",
price: 100
},
quantity: 1
}
],
shipping: {
name: "Jane Doe",
address: {
street: "456 Elm St",
city: "Springfield",
state: "Oregon",
zip: "97477"
}
}
}
];
To display the order history, we can loop through the orders
array and use optional chaining to access the properties
of each order. For example, to display the name of the product and its quantity, we can use the following code:
orders.forEach(order => {
order.items.forEach(item => {
const productName = item.product?.name;
const productQuantity = item.quantity;
console.log(`Product: ${productName}, Quantity: ${productQuantity}`);
});
});
If the item
object does not have a product property, item.product?.name
will return undefined
, and the code will not throw an error.
Also, to display the shipping address of an order, we can use the following code:
const shippingAddress = order.shipping?.address?.street ?? 'N/A';
console.log(`Shipping Address: ${shippingAddress}`);
If the order
object does not have a shipping property, order.shipping?.address?.street
will return undefined
and ?? 'N/A'
will assign the value N/A
to the shippingAddress
variable, thus preventing the code from throwing an error.
This way, we can use Optional Chaining and Nullish Coalescing to provide default values if a property is null
or undefined
.
Use case 2: Access deeply nested object properties
Suppose you have an object representing a customer record with nested properties for the customer’s name, address, and phone number. The object might look like this:
const customer = {
name: {
first: 'John',
last: 'Doe'
},
address: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zip: 10001
},
phone: '212-555-1212'
};
If you want to access the customer’s phone number, you can simply use dot notation:
const phone = customer.phone; // returns '212-555-1212'
But if you want to access a nested property that might be null
or undefined
, you can use Optional Chaining to safely access the property without causing an error:
const phone = customer.phone;
const zip = customer.address?.zip; // returns 10001
Without Optional Chaining, you would have to check whether the address property is truthy before attempting to access the zip
property:
const phone = customer.phone;
const zip = customer.address && customer.address.zip; // returns 10001
Use case 3: Provide default values
Another example of using Optional Chaining and Nullish Coalescing in a JavaScript application could be a social media platform feature allowing users to view and edit their profile information.
The data for the user’s profile is stored in a JavaScript object that looks something like this:
const user = {
id: 1,
name: "John Smith",
email: "john.smith@example.com",
bio: "Software developer and avid traveler",
social: {
facebook: "john.smith",
twitter: "jsmith",
website: "https://johnsmith.com"
},
preferences: {
theme: "dark",
notifications: true
}
};
To display the user’s social media links on their profile, we can use Optional Chaining to access the properties of the social object. For example, to display the user’s Facebook and Twitter links, we can use the following code:
const facebookLink = user.social?.facebook ?? "Add Facebook link";
const twitterLink = user.social?.twitter ?? "Add Twitter link";
console.log(`Facebook: ${facebookLink}`);
console.log(`Twitter: ${twitterLink}`);
Here, if the user
object does not have a social property or if it doesn’t have a Facebook or Twitter property, user.social?.facebook
or user.social?.twitter
will return undefined
and ?? "Add Facebook link"
or ?? "Add Twitter link"
will assign the default value to the variable, thus preventing the code from throwing an error.
To allow users to edit their profile information, we can use Nullish Coalescing to provide default values when updating the user’s preferences. For example, to update the user’s theme and notifications preferences, we can use the following code:
function updatePreferences(user, { theme = user.preferences.theme, notifications = user.preferences.notifications } = {}) {
user.preferences.theme = theme ?? user.preferences.theme;
user.preferences.notifications = notifications ?? user.preferences.notifications;
}
Here, if the theme or notifications values passed to the function are null
or undefined
, ?? user.preferences.theme
or ?? user.preferences.notifications
will keep the previous value of the theme or notifications and prevent the code from assigning null
or undefined
to the user’s preferences.
In this way, we can use Optional Chaining to safely access properties of nested objects and Nullish Coalescing to provide default values when updating data in the application.
Tips for effective use of optional chaining and nullish coalescing
Optional Chaining and Nullish Coalescing can be very useful when working with complex nested objects and data structures. Still, they also have their own quirks and edge cases of which we should be aware before using them. Let’s take a look at some:
- Be aware that the Optional Chaining operator
(?.)
only works withobjects
and will not work with primitive values such asnumbers
,strings
, orbooleans
. If you try to use it with a primitive value, you will get aTypeError
. Here is an example:
let person = { name: "John", age: 25 };
console.log(person?.name.toUpperCase()); // "JOHN"
let age = "Not specified";
console.log(person?.age.toString()); // TypeError: Cannot read property 'toString' of undefined
In the above example, using the Optional Chaining operator to access the name
property of the person
object works as expected, but trying to use it to access the age
property of a string variable throws a TypeError
.
- You can also use optional chaining in combination with the
destructuring assignment
to extract multiple properties from an object in a concise and readable way. For example, instead of writing
let street = user?.address?.street;
let city = user?.address?.city;
You could use the following:
let { street, city } = user?.address || {};
- When using the Optional Chaining operator on a function call, be aware that if the function returns
null
orundefined
, the entire expression will also evaluate tonull
orundefined
. So it’s important to check the return value of the function.
let user = { name: "John", age: 25 };
let getAge = () => user.age;
console.log(user?.getAge()); // 25
let getAddress = () => null;
console.log(user?.getAddress()); // null
In the first example, the function getAge
returns the value of user.age
, so the Optional Chaining operator works as expected. But in the second example, the function getAddress
returns null
, so the Optional Chaining operator also evaluates to null
.
- Understand that when using the Nullish Coalescing operator
(??)
with falsy values. The operator only returns the default value when the variable isnull
orundefined
, not when it’s “falsy” (false, 0,""
, etc.). In such cases, you should use a Conditional Ternary Operator? :
instead. For example:
let user = { name: "John", age: 0 };
console.log(user?.age ?? 25); // 0
console.log(user?.address ?? "Not specified"); // "Not specified"
In this example, the age
is falsy but not null
or undefined
, so the ??
operator will not work and will return the value of user.age
. But in the case of user.address
, either null
or undefined
, the ??
operator will return the default value Not specified
.
- When using the Nullish Coalescing operator with objects, be careful when the default value is also an object because JavaScript objects are compared by reference, not by value.
let user = { name: "John", age: 25 };
let defaultAddress = { street: "Unknown", city: "Unknown" };
console.log(user.address ?? defaultAddress); // { street: "Unknown", city: "Unknown" }
user.address = defaultAddress;
console.log(user.address ?? { street: "Unknown", city: "Unknown" }); // { street: "Unknown", city: "Unknown" }
In the first example, the default value is assigned to user.address
, but in the second example, the default value is not assigned because it’s the same object as the one in user.address
, so the ??
operator returns the same object.
- Use Nullish Coalescing when you want to provide a default value for a variable if it is
null
orundefined
. This allows you to avoid having to use the ternary operator? :
or a default parameter in a function. For example:
const obj = {
foo: undefined
};
// Without nullish coalescing
const value = obj.foo !== null && obj.foo !== undefined ? obj.foo : 'default value';
// With nullish coalescing
const value = obj.foo ?? 'default value';
- Always be mindful of the order of precedence of the operators. In javascript, the
?.
operator has higher precedence than the??
operator. Solet name = person?.name ?? 'John Doe'
will check forperson.name
first, and if it’snull
orundefined
, it will check for the default value.
So that your code behaves as expected and avoids any potential errors, It’s very important to be mindful of these when using optional chaining and nullish coalescing in JavaScript because they can lead to unexpected results if not used correctly.
Conclusion
In conclusion, mastering advanced JavaScript techniques such as Optional Chaining and Nullish Coalescing can greatly improve the clarity and simplicity of your code. Optional Chaining allows for concise handling of potentially null
or undefined
values, while nullish coalescing allows for a default value to be assigned in the case of null
or undefined
. These techniques have various use cases and can be effectively utilized with a bit of practice and careful consideration. Using Optional Chaining and Nullish Coalescing, developers can write more efficient, clean, and maintainable code.
Also, as these are both relatively new features in JavaScript, they may not be fully supported in all browsers. It’s always a good idea to check the current browser support for new features before using them in production code. You can use a tool like caniuse.com or check the browser’s developer documentation for more information.
I hope this article brought clear light to these topics, and as always, Happy Coding.