loading...

Optional chaining: What is it, and how can you add it to your JavaScript application right now?

nimmo profile image Nimmo Updated on ・3 min read

This post assumes that you're already transpiling your JS applications with Babel (version 7+). If you're not, then this probably isn't the feature to convince you to add that into your build process, but it's still a proposed language feature that is worth being aware of.

You've seen these errors before, hiding in your logs, in your automated test readouts, in your console, in your devtools: cannot read property "map" of undefined.

You spend time tracking down the error, and find the function in question:

const someFunction =
  someArray => 
    someArray.map(someOtherFunction);

You spend even more time looking at the code that called this function. Sometimes that array really might be undefined. In this scenario you decide that it is someFunction's responsibility to handle that. You update your code, and leave a helpful comment so that no-one else wastes time wondering why you're accounting for this:

const someFunction =
  (someArray) => {
    // Sometimes this is undefined: See [link to bug report]
    if (someArray === undefined) {
      return [];
    }

    return someArray.map(someOtherFunction);
  }

This works. But, you kind of liked the implicit return from the original example. A single expression in a function makes you feel more comfortable. No way anything else can sneak in there and cause problems. I like your thinking.

You try again, with a single expression this time, using a default value:

const someFunction = 
  (someArray = []) => 
    // Sometimes this is undefined: See [link to bug report]
    someArray.map(someOtherFunction);

This works. But, now your helpful comment is a bit weird. Will someone think that the output of this function is undefined, and accidentally account for that possibility elsewhere, even though this will always return an array? You imagine the confusion you've potentially caused, and the accumulated (hypothetical) cost to your company as a result.

You could make your comment clearer, but you want to solve this problem using JavaScript, not boring words.

You could resign yourself to a ternary, but that would mean having to type someArray an extra time. Let's look at a new alternative:

Enter optional chaining

With optional chaining, you have a new operator: ?.

You can use ?. on anything that you think might be undefined, which can save you from the most common and the most frustrating issues you see regularly in JS. For example:

const withoutOptionalChaining =
  something
    && something.someOtherThing
    && something.someOtherThing.yetAnotherThing

const withOptionalChaining =
  something
    ?.someOtherThing
    ?.yetAnotherThing

It's crucial to understand that if either someOtherThing or yetAnotherThing are undefined, then the withoutOptionalChaining example will be false, where the withOptionalChaining example will be undefined.

As you're aware if you've written JS for anything more than a day, undefined is not a function. But, what if that didn't matter?

const someValue = 
  someObject.someFunction?.() // returns `undefined` rather than a runtime error if `someFunction` is undefined!

 I'm in. But, how?

Fortunately, there's a Babel plugin for this: @babel/plugin-proposal-optional-chaining

Install that plugin with npm, and add it to your babel config via your chosen configuration option.

Depending on the rest of your Babel config, you may also find that you end up with an error about regenerator runtime not being defined. If so, you may need to add the @babel/plugin-transform-runtime as well, and configure it like so:

['@babel/plugin-transform-runtime',
  {
    regenerator: true,        
  },
]

If you're using ESlint, you'll find that it isn't too happy about this new operator. You'll also need to add the babel-eslint plugin to your ESlint config.

And that's it. Now you ought to be able to use optional chaining as much as you want to in your application.

Let's look again at that original code:

const someFunction =
  someArray => 
    someArray 
      // Sometimes this is undefined: See [link to bug report]
      ?.map(someOtherFunction)
      || [];

There we have it, another option for solving our problem. Do you always want to do this? Absolutely not: there are times when you probably do want a runtime error after all. But for the rest of the time, optional chaining is a great addition to your toolkit.

Disclaimer

Optional chaining is currently at Stage 1 in the proposal process, so whether or not you are willing to incorporate it right now is up to you.

Posted on by:

nimmo profile

Nimmo

@nimmo

I'm a software developer based in Newcastle Upon Tyne, England. I've got a wide range of experience in companies of varying sizes and cultures, and in roles of varying degrees of responsibility.

Discussion

pic
Editor guide
 

Anyway how to add it to rewired app?

 

This works for me:

const {
  override,
  addBabelPlugins,
} = require('customize-cra');

...

module.exports = override(
  ...
  addBabelPlugins('@babel/plugin-proposal-optional-chaining'),
  ...
);

 

I'm sorry, I'm not sure I understand your question. What do you mean?

 

Nice article.
But how can I use this geature right now in Chrome without npm?