So you have a bunch of foo && foo.bar
s lying around and you decide it’s time: you want to convert your JavaScript codebase to use optional chaining. You can’t wait to gaze upon all those foo?.bar
s with pride and excitement. You might think that you can just grep /([a-zA-Z]+)(?: |\n)+&&(?: |\n)+\1./$1?./
and be done, but, as I’ve discovered, it’s not quite that easy.
There are a few intrinsic properties of the optional chaining operator that might surprise you. Some of these might seem obvious, but it’s worth spelling them out, just in case you might have written the same bug I would have. 😉
What even is optional chaining
MDN is great, and their entry on the optional chaining operator is also great.
It’s definitely worth a quick skim if you’re not familiar, but even if you are familiar, it’s worth taking a look; it can be applied in some potentially surprising ways, like:
- array access:
foo?.[1]
;foo?.[bar]
- function access:
something?.()
- short-circuiting expressions:
thing?.[(expressionPotentiallyNeverInvoked)];
)
Negated conditions cannot be directly replaced
foo && foo.bar === 'baz'
can be represented as foo?.bar === 'baz'
. foo && foo.bar !== 'baz'
cannot be replaced with foo?.bar !== 'baz'
.
+—————————-+————-+
| condition | truth value |
+—————————-+————-+
| foo does not exist | false |
| foo.bar equals baz | true |
| foo.bar does not equal baz | false |
+—————————-+————-+
but you’ve replaced it with this one:
+—————————-+————-+
| condition | truth value |
+—————————-+————-+
| foo does not exist | true |
| foo.bar equals baz | true |
| foo.bar does not equal baz | false |
+—————————-+————-+
undefined
is not null
Shout-out to Chris Morgan, who pointed this out to me nearly three years ago!
undefined
is the absence of a value. null
is the intentional absence of a value. Maybe people regard the inclusion of both in the language as a mistake, but the distinction is often useful. Overture, Fastmail’s JavaScript framework, uses null
as a signal a lot.
null?.something
evaluates to undefined
.
If you want to take a blanket approach and use the optional chaining operator as much as possible, you have to be confident that you never check to see if this value is explicitly null
downstream. Of course, you likely aren’t taking advantage of special properties of null
either, like how null > -1 === true
but undefined > -1 === false
. At least, you don’t think you do. Right? Are you sure? 🙂
Speaking of false…
Say you have foo && foo.bar()
. Say you’re not using TypeScript, but say foo
can be a boolean or a function. If foo
is false
, foo?.bar()
is a TypeError.
If you’re not using TypeScript, it’s not a small amount of work to go through and make sure you’re not writing this bug.
You can short-circuit expressions early
MDN touches on this, but it bears spelling out: foo && foo.bar && foo.baz
can be replaced with foo?.bar && foo.baz
.
But: should you? Optional chaining hasn’t really fully sunk into my consciousness yet. I don’t feel like I miss other one-character operators when reading code — !
s in expressions are totally fine. Being tripped up on foo?.bar && foo.baz
can’t lead to any real misreadings of the code, either, other than maybe needing to take a second look.