DEV Community

Cover image for Put Down the Destructuring Hammer
Derek N. Davis
Derek N. Davis

Posted on • Originally published at derekndavis.com

Put Down the Destructuring Hammer

Destructuring is one of JavaScript's most handy features. Once I wrapped my head around the odd-looking syntax, I was a big fan of what it can do. I mean, what's not to love? If we want to destructure a property, we can do it.

Objects? We can destructure that.

const { firstName, lastName } = person;
Enter fullscreen mode Exit fullscreen mode

Arrays? We can destructure that.

const [person, setPerson] = useState(null);
Enter fullscreen mode Exit fullscreen mode

An array of objects in an object? We can destructure that too.

const {
  firstName,
  lastName,
  employmentHistory: [
     { company, startDate, endDate, title }
  ]
} = person;
Enter fullscreen mode Exit fullscreen mode

It even works on strings, believe it or not.

const { length } = "hello"; // But don't do this. Just no.
Enter fullscreen mode Exit fullscreen mode

What about if we want to default a value if there's not one? No problem.

const { firstName = 'Derek', lastName = 'Davis' } = person;
Enter fullscreen mode Exit fullscreen mode

But with all that power, there's potential for problems.

Naming Clashes

Once we go down the path of destructuring, we'll inevitably run into the next most common problem it causes: variable naming clashes.

const { firstName, lastName } = person1;
// whoops! can't do that.
const { firstName, lastName } = person2;
Enter fullscreen mode Exit fullscreen mode

firstName and lastName are taken. So what do we do? Destructuring has an answer for that.

const {
    firstName: person1FirstName, 
    lastName: person1LastName
} = person1;
const {
    firstName: person2FirstName,
    lastName: person2LastName
} = person2;

// ... later on ...

alert(`
    hello ${person1FirstName} ${person1LastName}
    and ${person2FirstName} ${person2LastName}!
`);
Enter fullscreen mode Exit fullscreen mode

We've renamed our properties to fix the error, but what have we gained? We have several hideous lines of JavaScript, and we can use person1FirstName without putting a dot in it.

Dot Notation to the Rescue

Check this out.

// the destructuring lines are gone! 

// ... later on ...

alert(`
    hello ${person1.firstName} ${person1.lastName}
    and ${person2.firstName} ${person2.lastName}!
`);
Enter fullscreen mode Exit fullscreen mode

If we use dot notation, we don't have to destructure anything, we don't have the variable naming conflict, we have less code, and it's more readable!

Let's look at another example.

The Lure of Shorthand Property Names

Shorthand property names are one of my favorite features in JavaScript. I love how clean the syntax looks.

// old school
setPerson({ name: name, city: city });

// shorthand property names. so clean.
setPerson({ name, city });
Enter fullscreen mode Exit fullscreen mode

But sometimes we can have tunnel vision when we're trying to use this feature. If what we have to destructure is deeply nested, we've only created more noise.

const {
    name,
    demographics: { address: { city } }
} = person; // a game of match the brackets

setPerson({ name, city });
Enter fullscreen mode Exit fullscreen mode

So what's the answer?

Dot Notation Again

We've gotten rid of the destructuring and all those brackets. It's so much more readable this way.

// no destructuring

setPerson({
  name: person.name,
  city: person.demographics.address.city
});
Enter fullscreen mode Exit fullscreen mode

But hey, maybe you don't want to use all the dots. Destructuring only the top level properties keeps things readable.

// just the right amount of destructuring
const { name, demographics } = person;

setPerson({
  name,
  city: demographics.address.city
});
Enter fullscreen mode Exit fullscreen mode

What's easy to forget is that dot notation and destructuring can be used in combination for better readability. For instance, if we want to pull out the properties of address, we can do this:

// not ideal
const {
    demographics: { address: { city, state, zip } }
} = person;

// so much better
const { city, state, zip } = person.demographics.address;
Enter fullscreen mode Exit fullscreen mode

Destructuring is one of those features that's great in its flat form, but when it becomes nested, the readability starts to degrade quickly.

Naming Ambiguity

Imagine this. You're trying to understand an area of your application you're not familiar with. You're 200 lines into one of the files, and you come across a variable called name. There's not a local declaration of it; it's just being used for something, and you have no idea what it is. So you go hunting and find this:

const { name, address, phone } = company;
Enter fullscreen mode Exit fullscreen mode

In this case, using destructuring created an overly generic variable name because it removed the context of where it came from. If it hadn't been destructured, company.name would have been totally clear. No variable hunting required.

When we decide to destructure something, keep it as close to where it's being used as possible, especially if the variable name is generic.

Summary

  • When destructuring causes naming clashes, it's a code smell. It might be okay, but then again, it also might be a sign you shouldn't be using destructuring.
  • Prefer keeping destructuring as flat as possible to avoid a mess of brackets. Using dot notation and destructuring in combination can help keep things flat.
  • Destructured objects should be as close as possible to where they are used to help readability. Overly generic names make code hard to understand.

Top comments (19)

Collapse
 
dorshinar profile image
Dor Shinar

I'm sorry, that's a straw man argument.
You hand picked bad examples of destructuring, and made the case that using plain old dot notation is superior.
There are a lot of cases where destructuring makes for much simpler code, of the top of my head I can pull out React functional components' props:

const A = ({a, b, c}) => ...
Enter fullscreen mode Exit fullscreen mode

I believe most people will agree that using props.a|b|c all over makes the code more verbose and not easier to understand.

Same goes for utility-like objects, where each property does not bare inherent relation to the others.

const { toSnakeCase } = stringUtils;
Enter fullscreen mode Exit fullscreen mode

Regarding ambiguity, your examples are examples where I don't know anyone who would destructure in the first place, but in other cases (like React's useState), destructuring helps you give better, more meaningful names to your objects.

Collapse
 
derekmt12 profile image
Derek N. Davis

I think we're on the same page. I'm just giving light to the fact that we shouldn't destructure everything just because we can. There are trade offs. I'm not saying it's all bad. I still like destructuring props and other things where it makes sense.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Well, I'm not trying to be argumentative but... (Ned Stark said that everything before the "but" is BS). As a React dev myself, I very much appreciate and want that props. preface before the props. Why? Because, I often find myself in the middle of a component where there is the "userId that was passed into the component" versus "the userId that we're currently looking at". In those cases, it's extremely helpful to know which was the "top-level" userId (i.e., the one that was passed into the component) versus the one that we're currently evaluating.

Granted, I'm not claiming that this is always the case with props. Perhaps, it's not often the case. But I very much enjoy looking throughout a component and being able to immediately spot the props - as opposed to any other type of variable.

Collapse
 
jackmellis profile image
Jack

Maybe it's just me but when I find I'm in a component where I can't tell the difference between props and internal state / computed values, I think my component probably needs a refactor

Collapse
 
moopet profile image
Ben Sinclair

This isn't a straw man - the title of the post is "put down the destructuring hammer". It's a play on the old, "when all you have is a hammer, every problem looks like a nail".

What they're saying is that destructuring is nice, but not to use it all the time in every situation without thinking about what you're doing, and whether you're going to make things more difficult for someone else to work on down the line.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I recently wrote an article exploring the same concept (dev.to/bytebodger/why-do-js-devs-h...). JS devs seem to have an aversion to namespacing. Although I destructure often, there are still many times when I purposely choose to use the object without destructuring - because it can make the code far more self-explanatory.

Collapse
 
derekmt12 profile image
Derek N. Davis

Great minds think alike I guess! "Well, destructuring effectively robs a variable of its context." So true. And you have a great point about the namespacing. That's even more broad than destructuring, but it makes a lot of sense. I had never thought about it.

Collapse
 
yoursunny profile image
Junxiao Shi • Edited

I find destruction handy for supplying a default value to an optional field.

async function resolve({
  hostname,
  type = "A",
}) {
  return doResolve(hostname, type);
}

async function resolve(opts) {
  return doResolve(opts.hostname, opts.type ?? "A");
  // could be worse if ?? is unavailable
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
aymericbouzy profile image
Aymeric Bouzy

you can even have a "bug" because of destructuring:

const bob = {
  name: "Bob",
  getName() {
    return this.name;
  },
};

bob.getName(); // "Bob"

const { getName } = bob;
getName(); // undefined
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bdunn313 profile image
Brad Dunn

There's one legitimate case for that when you are able to leverage some of the tree shaking and unused code elimination during bundling - if you set up webpack correctly, for example, you can import individual functions from lodash while still including the whole lodash package in your dependencies, but at bundle time drop all of the unused functions.

Other than this case, I agree that often context is more important - lodash is such a specific example haha

Collapse
 
aiosifelisl1 profile image
Andreas Iosifelis • Edited

I love how we can get the extension of a file name like so:

const [, extension] = filename.split('.')
Enter fullscreen mode Exit fullscreen mode
Collapse
 
merri profile image
Vesa Piittinen • Edited
const filename = 'filename.test.js';
const extension = filename.split('.').pop();
Enter fullscreen mode Exit fullscreen mode

Or if you must have destructure:

const [extension] = /[^.]+$/.exec(filename) ?? [];
Enter fullscreen mode Exit fullscreen mode

But whichever solution that actually is able to select the very last part will work.

Collapse
 
sleavely profile image
Joakim Hedlund

I get the feeling this approach won't work well with files such as foo.bar.js. You may want to reverse() after splitting and picking the first entry as extension instead.

Collapse
 
khorne07 profile image
Khorne07

Good article. Well explained. Thanks

Collapse
 
dskaiser82 profile image
Daniel Kaiser

Blasphemy! J/K I think there's value here. We as devs love our shiny new toys, but sometimes overkill is overkill

Collapse
 
arafel profile image
Paul Walker

Good advice; a lot of the examples fall into the "just because you can do something doesn't mean you should" category. :-)

Collapse
 
hamatoyogi profile image
Yoav Ganbar

Some good points here.