DEV Community

Cover image for APB: Demystified Destructuring
Ash
Ash

Posted on

APB: Demystified Destructuring

I love digital media - blogs, e-books, the more the better. But as a self-identifying bibliophile, I never go long without picking up an old-school, paper and ink book. When I want to deeply learn something I always come back to the printed word.
There's something about highlighting passages, and scribbling in the margins that cements information in my mind. I'm not alone in this, and there have been several studies that suggest students of all ages learn more, and retain more of what they learn, from physical books.

I'm currently working my way through the third edition of Eloquent JavaScript by Marijn Haverbeke. Though I consider JavaScript my primary language and felt comfortable with it before starting, I've learned something new, or gained a deeper understanding of something I already knew with every chapter.

During my study time the other night I came to a section about the ES6 features we all know and love, specifically destructuring. The section was short, more of a summary than a deep dive, but it made me realize I needed to know more about this ES6 feature - so I added it to the All Points Bulletin Board.


Question: What is Destructuring Assignment?

Short Answer: Destructuring is a succinct way to pull values out of an object and attach them to local variables in a single statement. It can be used on arrays or objects, allowing us to provide aliases, default values, and even complex expressions to obtain or replace data.


The Long Answer

The first time I saw destructuring was when learning React, and you'll see it in a lot of similar frameworks and libraries. It allows us to easily unpack multiple values from an array or object and assign them to variables in a single line.

Despite its name, destructuring isn't destructive - nothing is being destroyed. The original array is not mutated. Destructuring assignment allows us to literally deconstruct the object into its constituent parts, make copies and assign them to new local variables.

The feature came about because we had ways to construct multiple properties at the same time through object and array literals, but no comparable mechanism of extracting data - other than piecemeal assignments.

const numbersArray = [1, 2]; 
const x = numbersArray[0]; 
const y = numbersArray[1];
console.log(x, y);
//---> 1, 2
Enter fullscreen mode Exit fullscreen mode

Destructuring assignment works on the 2 most used data structures in JavaScript - arrays and objects. Array destructuring and object destructuring are very similar, with a few notable differences. We'll talk about array destructuring first.

Array Destructuring

At first glance destructuring looks a lot like an array or object literal - but flipped. Instead of a variable declaration on the left with the assignment on the right - the extracted values appear to the left, and the sourced variable on the right.

const numbersArray = [1, 2]; // array literal
const [ x, y ] = numbersArray; // destructuring assignment
console.log(x, y);
//---> [1, 2] 
Enter fullscreen mode Exit fullscreen mode

Note: I've used a subtle naming convention in the code above. When I use destructuring I pad the variables in the destructuring array between brackets or braces with a leading and trailing space. This helps me read my code later, scanning files I can easily identify when I'm using destructuring assignment.

Arrays are obsessed with position, just take a look at their built-in methods, and how those methods traverse elements. Array destructuring is no different, as we saw in the example above. The variables we created were assigned their values after being mapped to the value at the same index in the sourced array.

Using that syntax we know how to grab values from an array, but what else can we do?

  • We can assign values after declaration
let [a, b]; 
[ a, b ] = ["dog", "cat"];
Enter fullscreen mode Exit fullscreen mode
  • We can skip values

If the source variable contains values of no interest, they can be skipped with a comma and an empty space.

const dogs = ["Sparkles", "Rover", "Mosby", "Rufus"]; 
const [ a, , b, c ] = dogs;
const [ , , ,favorite ] = dogs;
console.log(a, b, c); 
//---> "Sparkles", "Mosby", "Rufus"
console.log(favorite);
//---> "Rufus"
Enter fullscreen mode Exit fullscreen mode
  • We can manipulate values with array methods

We can chain other methods that also return an array - like .split.

const name = "Mark Johnson"; 
const [ firstName, lastName ] = name.split(' '); 
console.log(firstName);
//---> "Mark"
Enter fullscreen mode Exit fullscreen mode
  • We can provide default values

What if we try to unpack more values than the sourced array contains? In that case those empty variables will return undefined, and no error will be thrown.

To avoid ambiguity, we can provide a default value using the assignment operator.

const employeeHours = [34, 40]; 
const [ employeeOne = 30, employeeTwo = 30, employeeThree = 30 ] = employeeHours; 
console.log(employeeThree);
//---> 30
console.log(employeeOne);
//---> 34
Enter fullscreen mode Exit fullscreen mode

These default values can take on much more depth, becoming complex expressions or function calls. They will only be evaluated if a value cannot be found.
Below I've used the .reduce method in a helper function to find the average of employeeHours and assigned it as a back-up for employeeThree.

const employeeHours = [34, 40]; 
const findAvg = (hours) => hours.reduce((a, b) => a + b, 0) / hours.length;
const [ employeeOne, employeeTwo, employeeThree = findAvg(employeeHours) ] = employeeHours; 
console.log(employeeThree);
//---> 37
Enter fullscreen mode Exit fullscreen mode
  • We can assign the Rest

If we extract only one value from the sourced iterable, then we only get that single value. What if we wanted to directly grab one or two values, but make sure the rest are still captured?
We can use 2 other ES6 features - the rest parameter and spread syntax. Using spread syntax (...) before a variable name creates the rest parameter. Sometimes you'll hear this referred to as the "rest pattern".

Using the rest pattern is like selectively placing a couple of values in special boxes that can contain only one thing, and throwing the rest into a larger, catch-all box in case we need them later on.

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 
const [ first, second, ...rest ] = numbers; 
console.log(rest);
//---> [3, 4, 5, 6, 7, 8, 9, 10]

const colors = ["red", "blue", "green", "yellow", "purple", "orangered", "goldenrod"];
const [ primary, secondary, accent, ...others ] = colors; 
console.log(others); 
//---> ["green", "yellow", "purple", "orangered", "goldenrod"] 
Enter fullscreen mode Exit fullscreen mode

Any name can be given to the rest parameter, but it must be proceeded by the spread syntax (...) and it must be the last variable in the destructuring array. If the rest parameter has a trailing comma it will throw an error.

  • We can extract values from nested arrays

So far we've been working with single layer arrays, but destructuring works on nested arrays too. As always, position is important and the corresponding item must be an array.

const styles = [["desktop", [800, 100, "relative"]], ["mobile", [500, 80, "absolute"]]]; 
const [ [ className, [ height, width, position ] ] ] = styles;

console.log(className);
//---> "desktop"
console.log(height);
//---> 800
Enter fullscreen mode Exit fullscreen mode

Now that we've seen the basics, let's look a couple of use cases for array destructuring.

Use Case 1: Working with returned arrays

Functions commonly return an array. Using array destructuring can make working with returned arrays DRY-er and easier to read.
In the function below, we return an array and assign their values using destructuring. You can see that we can skip values, assign the rest and more, just as before.

function dummy() {
    return [10, 50, 30, 50, 60];
}

const [ first, second, , ...rest ] = dummy();
console.log(first);
//---> 10
console.log(rest);
//---> [50, 60] 
Enter fullscreen mode Exit fullscreen mode

Use Case 2: Swapping values

One very handy use case of destructuring is swapping the contents of two variables. Before ES6 this required creating a temporary variable.

let first = "one"
let second = "two"
// temporary variable
let originalFirst = first;
// swap the values
first = second; 
second = originalFirst;
console.log(first, second)
//---> "two" "one"
Enter fullscreen mode Exit fullscreen mode

First a new variable, originalFirst, is created to capture the value of first, ("one"). Then first is directly reassigned to point to the value of second, ("two"). Finally second will be pointed to the value we captured in the temporary variable. If we didn't do this the original value of first would be lost upon reassignment.

Using destructuring removes the need for a temporary local variable, and several lines of code altogether. In the destructuring array we'll declare the variables, and then swap them in the assignment, effectively trading values.

let first = "one"
let second = "two" 
[ first, second ] = [second, first]; 
console.log(first, second); 
//---> "two" "one"
Enter fullscreen mode Exit fullscreen mode

Use Case 3: Regex patterns!

Regular expressions, regex for short, provide us a way to search a string against a pattern. Using the exec function returns an array where the first element is the entire match, and the following elements are the smaller captured matches.

To illustrate what I mean, we'll look at a common regex pattern - the date matcher.

// this is a regex pattern - and a handy one to know!
const datePattern = /^([a-z]+)\s+(\d+)\s*,\s+(\d{4})$/i; 

let [ fullDate, month, day, year ] = datePattern.exec("December 20, 2020") || []; 

console.log(fullDate, month, day, year); 
//---> "December 20, 2020" "December" "20" "2020" 
Enter fullscreen mode Exit fullscreen mode

The exec function takes in the pattern to be searched for, and the string to be searched. It returns an array, containing first the full match with all of its spaces and characters ("December 20, 2020"), and then the individual matches that were captured ("December" "20" "2020").

Note that after calling the exec function and passing in a string, we provided the logical || (or) operator and defaulted the return to be an empty array if no match could be found.

Though this post isn't really about regular expressions, they're an incredibly valuable tool to have in your belt. They can be used to search for word, date, and other patterns in text - capable of being as broad or specific as needed. Read more about regular expressions here.


Object Destructuring

Object destructuring is very similar to array destructuring, so we'll touch on it quickly, concentrating on the differences. We can extract properties much the same way we do with arrays, but instead of brackets, we use curly braces.

const dogs = {good: "Rover", gooder: "Sparkles", goodest: "Ace"}; 
const { good, gooder, goodest } = dogs;
console.log(good, gooder, goodest); 
//---> "Rover" "Sparkles" "Ace"
Enter fullscreen mode Exit fullscreen mode

If arrays are obsessed with position, objects are obsessed with name. That being the case, the property name must be spelled correctly with casing in mind, or it will return undefined.

const dogs = {good: "Rover", gooder: "Sparkles", goodest: "Ace"}; 
const { good, Gooder, goodest } = dogs;
console.log(good, Gooder, goodest); 
//---> "Rover" undefined "Ace"
Enter fullscreen mode Exit fullscreen mode

If we're not interested in a value we don't need to intentionally skip it. Instead, we can simply omit the property name. Position doesn't matter, we need only to be aware of the property names we're interested in, and their location relative to depth.

const dogs = {good: "Rover", gooder: "Sparkles", goodest: "Ace"}; 
const { good, goodest } = dogs;
console.log(good, goodest); 
//---> "Rover" "Ace"
Enter fullscreen mode Exit fullscreen mode
  • We can assign values after declaration - with one gotcha

If we assign a value to a variable without a declaration, we must wrap the assignment statement in parentheses.

let first, second; 
({ first, second } = {first: 1, second: 2}); // assignment statement
console.log(first, second);
//---> 1 2
Enter fullscreen mode Exit fullscreen mode

If these parentheses are left out the statement will be read as a block, instead of an object literal destructuring assignment. They are not required when the declaration is present.

  • We can provide default values and assign the Rest

We can capture the rest, and assign a default value, the same way we do in array destructuring - the only difference is the curly brackets.

const topDogs = {
  first: "Rover", 
  second: "Marshmallow", 
  third: "Kurt", 
  honorableMentions: {
    costume: "Sparkles", 
    personality: "Buccaneer"
  }
}; 

const { first, third, fourth = "Cocoa", ...rest } = topDogs;

console.log(first, third, fourth); 
//---> "Rover" "Kurt" "Cocoa"
console.log(rest);
//---> Object {honorableMentions: Object {costume: "Sparkles", personality: "Buccaneer"}, second: "Kurt"}
Enter fullscreen mode Exit fullscreen mode
  • We can provide an alias

If we want our identifier to have a different name than the property, we can provide an alias. Call the property name as usual, followed by a colon and the desired name.

const topDogs = {
  first: "Rover", 
  second: "Marshmallow", 
  third: "Kurt", 
}; 

const { first: gold, second: silver, third: bronze } = topDogs;
console.log(gold, silver, bronze);
//---> "Rover" "Marshmallow" "Kurt"
Enter fullscreen mode Exit fullscreen mode
  • We can extract values from nested objects

The properties on the objects we've seen so far have contained primitive data types, but they can also contain complex structures, like another object. We can use destructuring to access values in these nested structures.

const topDogs = {
  first: "Rover", 
  second: "Marshmellow", 
  third: "Kurt", 
  honorableMentions: {
    costume: "Sparkles", 
    personality: "Buccaneer"
  }
}; 

const { honorableMentions: { costume: bestDressed } } = topDogs;
console.log(bestDressed); 
//---> "Sparkles"
Enter fullscreen mode Exit fullscreen mode

I like to think of these statements as a map with legend. honorableMentions is not an identifier, or a variable. If we try to log it and peak at its guts, we won't find anything.
It just lets the compiler know to look for a nested object with name honorableMentions on the first level of the sourced object, reach into it and grab the value of the property with name costume and copy the value found there into our identifier bestDressed.

We can extract values an unlimited amount of levels down. We just need to keep track of how many levels deep we are (with the presence of brackets) and how many stops there are on the way (with property names).

Without destructuring we could accomplish the same result with dot or bracket notation.

const topDogs = {
  first: "Rover", 
  second: "Marshmellow", 
  third: "Kurt", 
  honorableMentions: {
    costume: "Sparkles", 
    personality: "Buccaneer"
  }
}; 

console.log(topDogs.honorableMentions.costume); 
//---> "Sparkles"
Enter fullscreen mode Exit fullscreen mode

Use Case: Destructuring props

Working with the props system in React often involves working with large, complex objects. Here destructuring can really shine - making components not only easier to read, but easier to write.

In this contrived example, we're passing a card object to a card viewing component through props.

import React from "react";
import "./styles.css";
import CardViewer from './CardViewer'; 

const cardData = {
  front: "What does padding refer to in CSS?", 
  back: "Padding refers to the space between the border of the element and the content of the element", 
  user_id: 1, 
  public: true, 
  active: true, 
  notes: ["if border-box sizing is used padding will not effect the size of an element", "padding 'pads the content'"]
}; 

export default function App() {
  const card = cardData; 


  return (
    <div className="App">
      <CardViewer card={card} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Without destructuring we have to repeatedly use the props prefix, and any further prefix needed, to get to the correct data in the CardViewer component.

import React, { useState } from "react";


const CardViewer = (props) => {
   const [ flipped, setFlipped ] = useState(false); 

   const flipCard = (e) => {
     e.preventDefault(); 
     setFlipped(!flipped);
   }


  return(
    <div onClick={flipCard}>
      <h3> {flipped ?
             `${props.card.back}` : 
             `${props.card.front}`}
      </h3>

      <div>
        <p>Notes:</p>
        <ul>{props.card.notes.map((note)=>{
          return <li>{note}</li>
        })} </ul>
      </div>

    </div>
  )
}

export default CardViewer; 
Enter fullscreen mode Exit fullscreen mode

Using destructuring we can easily grab the values we need in the component parameters, then in the return need only include the card prefix.

import React, { useState } from "react";


const CardViewer = ({ card }) => {
   const [ flipped, setFlipped ] = useState(false); 

   const flipCard = (e) => {
     e.preventDefault(); 
     setFlipped(!flipped);
   }


  return(
    <div onClick={flipCard}>
      <h3> {flipped ?
             `${card.back}` : 
             `${card.front}`}
      </h3>

      <div>
        <p>Notes:</p>
        <ul>{card.notes.map((note)=>{
          return <li>{note}</li>
        })} </ul>
      </div>

    </div>
  )
}

export default CardViewer; 
Enter fullscreen mode Exit fullscreen mode

We can take it even further with nested destructuring - grabbing only the things we're truly interested in. The resulting return reads almost like a sentence.

import React, { useState } from "react";


const CardViewer = ({ card: { front, back, notes: [ ...notes ] } }) => {
   const [ flipped, setFlipped ] = useState(false); 

   const flipCard = (e) => {
     e.preventDefault(); 
     setFlipped(!flipped);
   }


  return(
    <div onClick={flipCard}>
      <h3> {flipped ?
             `${back}` : 
             `${front}`}
      </h3>

      <div>
        <p>Notes:</p>
        <ul>{notes.map((note)=>{
          return <li>{note}</li>
        })} </ul>
      </div>

    </div>
  )
}

export default CardViewer; 
Enter fullscreen mode Exit fullscreen mode

Destructuring is another invaluable ES6 feature, allowing us to extract properties and values the same way we can construct them with object literals. Destructuring saves time, reduces confusion and can result in elegant, readable code.

The next time you're working with complex data structures, keep this feature in your back pocket and you might just save yourself some time and headaches.


Resources

Eloquent JavaScript - Marijn Haverbeke

The Babel Replit - Always a good place to toss some code in and learn what it does under the hood

Array Destructuring in ES6 - FreeCodeCamp

Destructuring Assignment - MDN Web Docs

ES6 Destructuring: The Complete Guide - Glad China, CodeBurst

Destructuring Assignment - Javascipt.info

Destructuring - exloringjs.com

Destructuring Regular Expression Matching - Marius Schulz

JavaScript Object Destructuring - Dmitri Pavlutin

Destructuring & Function Arguments - David Walsh

Destructuring Props in React - Lindsay Criswell, Medium

πŸ¦„ Thanks for reading!

Oldest comments (5)

Collapse
 
hanna profile image
Hanna

Great post! haven't seen such a in depth article on destructuring, definitely a good read and overall informative post.

Collapse
 
ash_bergs profile image
Ash

Thanks for reading, and the feedback. I was concerned it might be a little too long, but wanted the material to feel complete πŸ™‚

Collapse
 
j0hnk profile image
John

Awesome! This was a great read and I found myself saying "aha..." and "ooh.. cool" quite a few times. Even though I don't use react that much I could easily extract my own use cases. Thanks!

Collapse
 
ash_bergs profile image
Ash • Edited

Thank you! Getting to be part of one "aha" moment is a wonderful feeling

Do you use another framework? I've worked with React for a lot of the 'learning phase' of becoming a developer, but I'm interested in taking stock of other popular approaches to grow my knowledge.

Collapse
 
j0hnk profile image
John

Where I work now we do mostly angular/typescript stuff. Coming from a java backend role it's been quite a pleasant learning path but there are still plenty of gaps in areas like the one in your post :)