Start fully leveraging the destructuring


Photo of my chubby face smiling to you

Piotr Staniów • Posted on 16 Apr 20203 mins

#JavaScript
#ES6

This article is a part of Frontend 101 series.

Having explained destructuring in one of the teams I've been working for, I prepared one tricky example to both show how powerful the destructuring syntax is, and to structure the knowledge we just shared. Would you be able to tell, without actually running the code, what the following snippet would print in the output?

const c = 'd';
const { a: { [c]: b = 2 } = { b: 1 } } = { b: 0 };
console.log(b); // What does it print?

You can vote for correct answer on Twitter.

The code above is of course an extremely exaggerated example, combining most of the destructuring syntax features, and we probably wouldn't like to see this snippet anywhere in our codebase.

However, each of these features on its own is quite powerful and can help simplifying your code, moving focus away from implementation details. The destructuring syntax was first introduced in JavaScript in 2015, with the release of ECMAScript 6. Unfortunately, it is still underestimated and many developers aren't fully leveraging it.

The basics

First things first, let's discuss what destructuring actually is. The basic use case is to declare a variable, a variable whose value will be equal to the value of one of an object property.

These two functions are equivalent, but the latter is using destructuring:

function classic(obj) {
    const prop = obj.prop;
    return prop;
}

function destructuring(obj) {
    const { prop } = obj;
    return prop;
}

The latter function defines prop variable, that will be equal to obj.prop. It's exactly like if you declared it using const prop = obj.prop;.

The syntax can be also used to declare multiple variables from multiple properties of the object, just like you can declare multiple variables after const (or let, or var), using comma delimiter:

function classic(obj) {
    const propA = obj.propA,
          propB = obj.propB;
    return propA + propB;
}

function destructuring(obj) {
    const { propA, propB } = obj;
    return propA + propB;
}

Destructuring function parameters

The destructuring syntax can be also used in other place where variables are declared — in the function declaration. In the following example, we will attempt to destructure whatever is passed as a first argument to the function:

function destructuring({ propA, propB }) {
    return propA + propB;
}

Let the fun begin — default values

What happens though, if the object is passed without the property that we want to destructure? We could of course "default" it using the alternative operator: return (propA || 17) + (propB || 13); — it isn't very legible though, is it?

Fortunately, the destructuring syntax also allows you to default particular variables in much cleaner way — the value of it can be any expression, be it numeric value, object literal or function call:

function destructuring({
    propA = 17,
    propB = 13,
    propC = { newObject: 3 },
    propD = functionCall(),
}) {
    console.log(propC, propD);
    return propA + propB;
}

Usually this is where the usage of destructuring ends on, at least in a typical project. But we want to be destructuring experts, and leverage all available patterns, right?

Aliases — in case of names collision

Sometimes there will be cases, when you would like to destructure a property whose name is already taken. It can still be conveniently declared with the destructuring syntax and its aliasing feature.

The following snippet uses the getBoundingClientRect function from native DOM API, that returns an object that has, amongst others, width and height properties:

// Without destructuring
function getTotalDimensionsClassic(sidebar, content) {
    const sidebarDimensions = sidebar.getBoundingClientRect();
    const contentDimensions = content.getBoundingClientRect();

    // What if you needed to default these values?
    return {
        width: sidebarDimensions.width + contentDimensions.width,
        height: sidebarDimensions.height + contentDimensions.height,
    };
}

// With destructuring
function getTotalDimensionsDestructuring(sidebar, content) {
    const { width: sidebarWidth, height: sidebarHeight } = sidebar.getBoundingClientRect();
    const { width: contentWidth, height: contentHeight } = content.getBoundingClientRect();
    return {
        width: sidebarWidth + contentWidth,
        height: sidebarHeight + contentHeight,
    };
}

Nested properties

Sometimes responses from APIs that we use have wicked structure and there is nothing we can do about that. Can't we? Actually, if there are only some nested pieces of the response that you are interested in, you can leverage your new friend — destructuring syntax — to pick only those fruits that you need:

const response = {
    count: 17,
    page: 1,
    data: {
        normalized: {
            ids: ['uuid-1'],
            entities: {
                'uuid-1': { id: 'uuid-1' },
            },
        },
    },
};
const { count, page, data: { normalized: { ids, entities } } } = response;

In this example, variables defined are: count, page, ids and entities. Notice that data and normalized are not defined here.

Adding more dynamic elements

Feeling overwhelmed a bit? The destructuring syntax is rich and trust me, even though initially it may look like a lot to learn, it quickly becomes your second nature. The more you use it, the more obvious and liberating it gets, just don't you combine everything in one line, just like I did in the first example.

Speaking of which, there's still one feature used in that example, that we haven't discussed yet, that is: dynamic values.

Sometimes, you want to destructure property of an object, but you have that property name under a variable. It may be handy, if you're just interested in that particular property:

const sortingOrder = getSortingOrder();
const sortedValues = {
    asc: [0, 1, 2, 3],
    desc: [3, 2, 1, 0],
};
const { [sortingOrder]: values } = sortedValues;

Notice, that with the dynamic properties, the name can't be the same as of the variable — it's already taken by itself. Hence, the dynamic destructuring always comes in pair with aliasing that we've learned a few paragraphs before.

Puzzle solved

Let's take another look at the example we started with:

const c = 'd';
const { a: { [c]: b = 2 } = { b: 1 } } = { b: 0 };
console.log(b); // What does it print?

It combines nearly all features we have discussed above:

  • Nested destructuring of properties
  • Defaulting values
  • Dynamic property destructuring
  • Aliasing

So what's the output of it? The answer is 2.

Explanation

The destructured object is { b: 0 }. We start with destructuring property a from it, which is undefined, so the default value { b: 1 } will be used. Then, we have a nested destructuring that attempts to destructure dynamically defined property — the key is stored in c variable. Using the value of the variable, we try to destructure property d from the value we defaulted to ({ b: 1 }). But there's no d property, so we are again using default value, the one that is in nested destructuring — 2.

"

The only defined variables here are b and c. All others — a and d — are not declared and referencing them would result in throwing ReferenceError.

A final word

I hope you have enjoyed this puzzle and you can now write your own. With that knowledge, be careful when writing production code — with great power comes great responsibility! It is great when you use these features deliberately and shift the focus for those reading the code from mundane declarations to the actual logic — but remember, using them all in one place would probably make the code rather more convoluted than concise. 😉

TypeScript

If you are using TypeScript, make sure that you read my article that describes how to mix destructuring syntax and types. 🚀

Stay connected

Stay up to date with recent articles — subscribe to the newsletter.
Unsubscribe at any time with a link in the email.

© Piotr Staniów's Blog 2023

Opinions are my own and do not represent the views of my current or past employers.