mhoc

Let's say you have a React component that accesses a few properties on an input object.

const UserWelcome = ({ user }) => {
  const name = `${user.firstName} ${user.lastName}`;
  return (
    <div>
      <span>Hi {name}</span>
      <span>You have {user.unreadCount} unread messages.</span>
    </div>
  );
}

A programmer might conclude, after reading this, that its ripe for destructuring.

const UserWelcome = ({ user }) => {
  const { firstName, lastName, unreadCount } = user;
  const name = `${firstName} ${lastName}`;
  return (
    <div>
      <span>Hi {name}</span>
      <span>You have {unreadCount} unread messages.</span>
    </div>
  );
}

Maybe you'd even go one step further.

const UserWelcome = ({ user: { firstName, lastName, unreadCount }}) => {
  const name = `${firstName} ${lastName}`;
  return (
    <div>
      <span>Hi {name}</span>
      <span>You have {unreadCount} unread messages.</span>
    </div>
  );
}

The meaningful question after this exercise should be: Have we produced code that is valuably more readable? As with all things in software, there's no hard-and-fast rule which says “destructuring is always a good thing.”

In the second and third examples, a natural question comes to mind: Where did name and unreadCount come from? You can imagine this being a much more substantial problem in larger React components, where you're dealing with a variable being referenced dozens of lines away from its destructure; in other words, we've accidentally introduced a form of action-at-a-distance by hiding the origin of some destructured variable with a generic name.

Code is written once, and read hundreds of times. Every decision an engineer makes should be motivated primarily by the needs of the people who will read this in the months and years later.

In this context: Destructuring is vastly overused, as a crutch to reduce the amount of keystrokes between the ticket and a PR. But, it isn't useless; imagine a similar example to those above.

const UserWelcome = (props) => {
  const name = `${props.user.firstName} ${props.user.lastName}`;
  return (
    <div>
      <span>Hi {name}</span>
      <span>You have {props.user.unreadCount} unread messages.</span>
    </div>
  );
}

This example seems insane; yet it doesn't come with no advantages. Its now immediately clear whether a variable is computed within the component, such as name, or whether it is provided as a prop.

But, I wouldn't suggest doing this; it hurts readability too much. Destructuring that into each individual prop makes sense here. Where, then, do you draw the line?

When To Destructure

I can distil my thoughts on the matter down to a question one should ask themselves when making this decision, and a guideline. These aren't rules; there are no rules when it comes to these things; but rather just things to think about and help guide a decision.

Will I, or anyone, ever have reason to question the context of this variable? Is the name relatively generic? Could the name easily be overloaded within the component or function?

If so, it may make sense to add a layer of context to its name; this context is immediately and already available by simply not destructuring.

Aim for zero or one layers of property access on an object. More may generally indicate a need for destructuring, or the use of a utility like lodash.get.

Property Aliasing During Destructuring

It may be alluring to simplify destructured properties through a property alias.

const UserWelcome = (props) => {
  const { user: { firstName: userFirstName } } = props;
  ...
}

This feature is unusually risky from a readability point of view, and should be used cautiously. The decision to alias that property, instead of using it directly, increases the scope of variable names that engineers need to keep in their heads when reading this code.

In previous examples, the scope looked like user, firstName, lastName, and unreadCount.

In this new example, all of those previous names are still likely floating around; in other components, or even elsewhere in this one. They form the domain language of what a User even is. But, instead of re-using that domain language, which is readily available to us, we introduce a new word: userFirstName.

Its obvious what this does. But, I can guarantee many projects' components and domain language will inevitably become so complex that, eventually, an engineer will come along and think to themselves “where did that thing come from.”

Use destructure aliasing very sparingly indeed.