Let’s face it: JavaScript objects sometimes can be both awesome and scary. They are super flexible, but they often lead to a big problem.
Let’s take the following example and pretend that this data comes from a database:

{
  user: {
    name: "John",
    surname: "Doe",
    birthday: "1995-01-29",
    contacts: {
      email: "foo@bar.com",
      phone: "000 00000"
    },
    laguages: ["english", "italian"]
  }
}

We may want to access the user phone number… but what if he hasn’t compiled it yet? What if the object above has changed for some reason, and we don’t have the contatcs object anymore?

const userData = {
  user: {
    name: "John",
    surname: "Doe",
    birthday: "1995-01-29",
    laguages: ["english", "italian"]
  }
}

const userPhone = userData.user.contacts.phone;
// => Uncaught TypeError: Cannot read property 'phone' of undefined

Holy guacamole! That is such a frequent error! And how many times did you come up with the following solution?

const userData = {
  user: {
    name: "John",
    surname: "Doe",
    birthday: "1995-01-29",
    laguages: ["english", "italian"]
  }
}

const userPhone = userData.user && userData.user.contacts && userData.user.contacts.phone;

Of course it works… but there are two problems with this code:

  1. Hard to maintain and ugly code.

  2. If contacts object does not exist, userPhone constant will be false. So imagine that we need to access a property hasPaid. I think that it should be return undefined rather than false if the property does not exist, doesn’t it?

Quick and dirty solutions

Great libraries like Ramda and Lodash provides their own way to handle this problem:

Lodash:

import _ from "lodash";

_.get(userData, "user.contacts.phone");

Ramda:

import R from "ramda";

R.path(['user', 'contacts', 'phone'], userData);

I also wrote a tiny ~300bytes library called **MJN** that can handle this problem!

import maybe from "mjn";

maybe(userData, "user.contacts.phone", () => "Phone does not exist");

Well, as you can see, we can handle that problem in few different ways… but not natively (yet!).

Other Languages

As you may guess, that particular problem exist in other languages… but they handle it! Let’s see some examples:

Groovy:

def userPhone = userData?.user?.contacts?.phone

Ruby:

userPhone = userData.try(:user).try(:contacts).try(:phone)

C#:

string userPhone = userData?.user?.contacts?.phone;

As you can see, Groovy and C# implements a similar control flow… and guess how does the EcmaScript Optional Chaining Proposal look?

EcmaScript Proposal

const userPhone = userData?.user?.contacts?.phone;

Ta-da! EcmaScript proposal looks very similar to the C# and Groovy implementation! I love it!
Let’s see how to use it today:

First of all, install Babel in your project:

npm install --save-dev @babel/core @babel/cli @babel/plugin-proposal-optional-chaining

Then make sure that your package.json file looks like this:

{
  "name": "my-project",
  "version": "0.0.1",
  "scripts": {
    "build": "babel index.js -d lib",
    "start": "node lib/index.js"
  },
  "devDependencies": {
    "@babel/cli": "^7.0.0",
    "@babel/core": "^7.0.0",
    "@babel/plugin-proposal-optional-chaining": "^7.2.0"
  }
}

Last but not least, create a .babelrc file and fill it with the following JSON:

{
  "plugins": [
    ["@babel/plugin-proposal-optional-chaining", { "loose": false }]
  ]
}

Ok great! Now we’re ready to go!

Getting Data

We’re gonna start with the example above: how do we get nested data?

const userData = {
  user: {
    name: "John",
    surname: "Doe",
    birthday: "1995-01-29",
    contacts: {
      email: "foo@bar.com",
      phone: "000 00000"
    },
    laguages: ["english", "italian"]
  }
}

const phone = userData?.user?.contacts?.phone;

console.log(phone); // => "000 00000"

So easy! And what if we try to access a property that does not exist?

const profilePicture = userData?.user?.images?.profile;

console.log(profilePicture); // => undefined

We got undefined! Great!

Deleting Properties

Deleting properties is even easier!

delete userData?.user?.languages;
delete userData?.user?.contacts;

console.log(userData);

/*
  user: {
    name: "John",
    surname: "Doe",
    birthday: "1995-01-29"
  }
*/

Imagine doing this without the Optional Chaining Operator... how many if statement would you write?

Did you like this article? Consider becoming a Patron!

This article is CC0 1.0 (Public Domain) licensed.