DEV Community

Cover image for 4 Ways to Level-Up Your JS Destructuring and Spread Syntax
Scott O'Dea
Scott O'Dea

Posted on • Updated on

4 Ways to Level-Up Your JS Destructuring and Spread Syntax

If you're a fan of Javascript and reading dev articles, you might share my addiction of clicking on every article with a title 'X number of cool JS tricks'.
I've probably read 40-50 of them. Most of the ones that I found really handy were to do with destructuring and spread syntax. I thought I'd consolidate the best tips here.

1. Destructuring an array with index numbers

Destructuring is a great way to pull exactly what you need from a data set. However, with arrays it can be frustrating if you need a property a few indexes down the line and don't care about the first few.

I'm sure you've seen syntax like this before where properties are omitted using commas:

const fruits = ['🍎', 'πŸ₯­', 'πŸ‰', 'πŸ‹']
const [,,, lemon] = fruits
console.log(lemon) // πŸ‹
Enter fullscreen mode Exit fullscreen mode

To me this looks messy. A developer has to look at the number of commas to understand which properties were taken from the array and if you need grab a few values not next to each other it can become very unwieldy.

Thankfully, in JS, arrays are really just objects under the hood. The index is the key and the array property is the value.

const fruits = ['🍎', 'πŸ₯­', 'πŸ‰', 'πŸ‹']
// {0: '🍎', 1: 'πŸ₯­', 2: 'πŸ‰', 3: 'πŸ‹'}
Enter fullscreen mode Exit fullscreen mode

What this means is we can use object destructuring on our array. We can use the index of the properties we want to access to pull them from the array.

const fruits = ['🍎', 'πŸ₯­', 'πŸ‰', 'πŸ‹']
const {3: lemon} = fruits
console.log(lemon) // πŸ‹
Enter fullscreen mode Exit fullscreen mode

Now a developer can see exactly which property you took by checking the index. Also if you need multiple values that are not neighbours there's no need to awkwardly count the amount of commas that should be between them.

2. Filtering with spread and destructure

Removing properties from an object can be pretty straightforward. Simply use the delete keyword and you're on your way.

const food = {watermelon: 'πŸ‰', apple: '🍎', mango: 'πŸ₯­', fries: '🍟'}
delete food.fries
console.log(food) // {watermelon: 'πŸ‰', apple: '🍎', mango: 'πŸ₯­'}
Enter fullscreen mode Exit fullscreen mode

This is fine if you're happy with your object being mutated.
However, objects can be passed around into different functions and accessed in several places, so suddenly removing a property could cause issues.
For this reason it's usually a safer idea to leave your initial object intact and create a new one for your filtering.

We can achieve this by using destructuring to separate the unwanted property or properties from the rest of the object.

const food = { watermelon: 'πŸ‰', apple: '🍎', mango: 'πŸ₯­', fries: '🍟' }
const { fries, ...fruits } = food
console.log(fruits) // { watermelon: 'πŸ‰', apple: '🍎', mango: 'πŸ₯­' }
Enter fullscreen mode Exit fullscreen mode

Note that compared with mutating the original object, we now have a new object with a suitable name to indicate more clearly what the object now contains.

One caveat with this method is linters may underline the property you're removing (in this case fries) as the variable won't be referenced anywhere.

3. Optional Object Building

Sometimes we want to build an object conditionally. I usually find myself in such a predicament when I need to send data to an api. An api request could need different parameters depending on the state the application is in.

One approach might be to use some if statements.

const params = { }
if (id) params.id = id
if (pageNumber) params.pageNumber = pageNumber
await axios('https://endpoint.com/data', {params})
Enter fullscreen mode Exit fullscreen mode

This works fine, but can get bloated quickly. I also personally prefer to instantiate the object with the properties it needs from the get-go.

We can achieve this by taking advantage of the fact that falsey values will be ignored when spread into an object.

const params = { id: 131, ...false }
console.log(params) // { id: 131 }
Enter fullscreen mode Exit fullscreen mode

So to conditionally add properties we need to check the property isn't falsely and if so add it to our object.

const params = { id: 131, ...(pageNumber && {pageNumber}) }

// Page number falsey value
console.log(params) // { id: 131 }

// Page number truthy value
console.log(params) // { id: 131, pageNumber: 2 }
Enter fullscreen mode Exit fullscreen mode

So breaking this down, we start with the spread syntax for our conditional property. We then add parentheses to allow us to group our short circuit logic.

If pageNumber is a falsey value the logical && will exit with a falsey value which won't add anything to our object when spread.

If pageNumber is truthy however, the short-circuit logic will return the property we want to add and spread it into our object.

So refactoring the api example.

const params = {
  ...(id && { id }),
  ...(pageNumber && { pageNumber })
}
await axios('https://endpoint.com/data', { params })
Enter fullscreen mode Exit fullscreen mode

To me this is much nicer. We've avoided instantiating an empty object at the beginning like with the if statement example and all possible params that might be sent to the api can be seen directly on the object rather than combing through the if statements for possibilities.

4. Destructuring length from array

Relating to the first tip, you can take it a step further by destructuring the length of an array.
You might ask how this is useful, but say you needed the last element of a huge dynamic array. You couldn't destructure this last element with either the comma method or the index method, as the last element would be a different index as data was added. So you could use the length property to grab the last element when destructuring.

const bigDynamicArray = ['✌','πŸ˜‚','😝','😁','😱','πŸ‘‰','πŸ™Œ','🍻','πŸ”₯','🌈','β˜€','🎈','🌹','πŸ’„','πŸŽ€','⚽','🎾','🏁','😑','πŸ‘Ώ','🐻','🐢','🐬','🐟','πŸ€','πŸ‘€','πŸš—','🍎','πŸ’','πŸ’™','πŸ‘Œ','❀','😍','πŸ˜‰','πŸ˜“','😳','πŸ’ͺ','πŸ’©','🍸','πŸ”‘','πŸ’–','🌟','πŸŽ‰','🌺','🎢','πŸ‘ ']

const { length, [length-1]: lastItem } = bigDynamicArray
console.log(lastItem) // πŸ‘ 
Enter fullscreen mode Exit fullscreen mode

So first we pull out the length. We then use a computed property name to access correct index and finally give a name to our last item.

I will add this tip is approaching the territory of too clever for its own good. You don't want to burden other developers with understanding some abstract functionality for what could have been a rudimentary, yet simple to understand piece of code.

Thanks for reading let me know if there's anything I missed or if you have some better tips and happy JS'ing!

Discussion (8)

Collapse
jonrandy profile image
Jon Randy • Edited on

Nice! Some advanced stuff I'd never considered here. If you combine it with metho-number you could even do stuff like this:

const {[bin]: binary, [hex]: hexidecimal, [base(8)]: octal} = 16

console.log(binary) // "10000"
console.log(hexidecimal) // "10"
console.log(octal) // "20"
Enter fullscreen mode Exit fullscreen mode
Collapse
lukeshiru profile image
Luke Shiru

This could also look something like this, which might look more "familiar"

const { bin, hex, base } = wrapper(16);

bin; // "10000"
hex; // "10"
base(8); // "20"
Enter fullscreen mode Exit fullscreen mode

Still, it's an interesting approach to use symbols to extend objects safely ^_^

Collapse
scottodea profile image
Scott O'Dea Author

Cool! Taken it to overdrive. Although, I'd say this relates to my last point about code readability. Nice for personal projects but a pain when someone pulls you aside to ask what is going on with the line of code.

Collapse
jonrandy profile image
Jon Randy

Yeah - WTFs per minute in a code review of that would likely be off the charts

Collapse
thumbone profile image
Bernd Wechner

"I will add this tip is approaching the territory of too clever for its own good. You don't want to burden other developers with understanding some abstract functionality for what could have be a rudimentary, yet simple to understand piece of code." - I'm glad you ended with this. I was all into just that when I read:

const { fries, ...fruits } = food
Enter fullscreen mode Exit fullscreen mode

That blows my mind. Am I misunderstanding or are your really declaring fruits (implicitly as an array) and destructuring it on the LHS to receive food?

Like wow, I have to wonder if I want to know that ;-). And am tempted to read more on destructuring to gain some guru cred.

But then this:

const { length, [length-1]: lastItem } = bigDynamicArray
Enter fullscreen mode Exit fullscreen mode

What? Now arbitrarily the first entry is not a key in RHS (as fries was) but the length of RHS? By virtue of? What? This has me bamboozled and again I'm like: Do I really wanna know? That's some weird stuff going down there ...

Collapse
scottodea profile image
Scott O'Dea Author • Edited on

Hey Bernd. I think you're confusing array style destructuring with object style destructuring. The order in which you destruct properties does not matter when using '{}' style destructuring.
Ex. const {a, b, c} = {c: 'foo', a: 'bar', b: 'baz'}
However, when destructuring using array syntax the order definitely matters as the properties pulled are ordered by index.
ex. const [firstElement, secondElement] = ['foo', 'bar']

Collapse
hnicolas profile image
Nicolas HervΓ©

And what about const lemon = fruits[3]; ?

Collapse
scottodea profile image
Scott O'Dea Author

This is the correct approach for a single element, but what if you needed to pull 3 or 4 elements from the array? Granted I used a single element in my example, but just trying to keep it simple to understand.