Safely get deep nested props with JavaScript’s proposed nullish coalescing and optional chaining operators.
If you’ve written any JavaScript, you’ve probably seen this while working with deeply nested objects more times than you’re willing to admit:
Uncaught TypeError: Cannot read property 'length' of undefined
Optional chaining operator
const pageTitle = props.params?.suppliedTitle;
If props.params.suppliedTitle
is null
or undefined
instead of throwing an error, it will return undefined
.
Nullish coalescing operator
const pageTitle = suppliedTitle ?? "Default Title";
The nullish coalescing operator looks similar to ||
at first, but wont short circuit if the value of suppliedTitle
is false
or anything which coerces to false
.
The old method
There are many ways to solve this but usually involves highly verbose expressions, or third party library functions. Here are examples of the two:
// Our example object
const response = {
posts: [
{
body: '...',
comments: [
{
body: '...',
showEmail: false,
}
],
},
];
};
// Plain JS way
const showEmail =
response.posts &&
response.posts.length &&
response.posts[0].comments &&
response.posts[0].comments.length &&
response.posts[0].comments[0].showEmail;
// Using Ramda
const showEmail = R.path(['posts', 0, 'comments', 0, 'showEmail'], response);
As you can see it gets unwieldy quickly. Both of these will simply return undefined
if the value is not accessible, instead of throwing an error.
Returning a default value
You can return a default value like so:
// Plain JS way
const showEmail =
(response.posts &&
response.posts.length &&
response.posts[0].comments &&
response.posts[0].comments.length &&
response.posts[0].comments[0].showEmail) ||
true; // Bug, always returns true
// Using Ramda
const showEmail = R.pathOr(
true,
["posts", 0, "comments", 0, "showEmail"],
response
);
While the Ramda example is fine, the plain JS way will actually always evaluate to true because it’s value is false
and forces the code to short circuit the ||
.
New way
The null coalescing operator and optional chaining operator attempt to solve these problems in a succinct way.
// Return undefined if property doesn't exist
const showEmail = posts[0]?.comments?.[0]?.showEmail;
// Return default value of true if property doesn't exist
const showEmail = posts[0]?.comments?.[0]?.showEmail ?? true;
This works regardless if the values is false
or not, and will only short circuit if the value is undefined
or null
. The code is very easy to understand what is actually doing compared to previous examples.