JS Tip: Simple Array Of Unique Items

A co-worker showed me a simple trick for getting an array of unique, simple values. Let's say we were combining two arrays of message id's:

view plain print about
1let arr = [19,22,7,12,6,85];
2let arr2 = [22,8,3,19,45];
3let newArr = [...arr, ...arr2];
4// newArr equals [19, 22, 7, 12, 6, 85, 22, 8, 3, 19, 45]

This gives us a new array, combining the values of the first two. But, we often only want the unique values. Rather than looping over every item, checking for dupes, etc, we can take advantage of the new Set object. A `Set` lets you store unique values of any type, and automatically tosses duplicates. As an iterable, it's easy to convert it from an Array like object to a true array.

view plain print about
1newArr = Array.from(new Set(newArr));
2// newArr now equals [19, 22, 7, 12, 6, 85, 8, 3, 45]

And, being an array of numerics, we'd likely want to sort it in numeric order. We can do this with Array.sort().

view plain print about
1newArr = Array.from(new Set(newArr)).sort();
2// Not exactly, now it reads [12, 19, 22, 3, 45, 6, 7, 8, 85]

OK, so that seems a little weird, until you read that documentation for `sort()` that I linked to above

The default sort order is built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

Well, that seems a bit of a bummer. But, you can get around this by using the optional `compareFunction` argument of the `sort()` method.

view plain print about
1newArr = Array.from(new Set(newArr)).sort((a,b) => a - b);
2// That's better! Now it reads [3, 6, 7, 8, 12, 19, 22, 45, 85]

And there you have it. Simple, unique value array. The `Set` object allows for any type, so you could use complex objects as well, but again you would have to provide a custom `compareFunction` for handling the `sort()`.

Fun With Destructuring

If you aren't compiling your JavaScript (well, EcmaScript...) code with Babel, you're probably missing out on some of the best features of the evolving language. On the other hand, if you're working in React or Angular every day, you've probably come across some of the most dynamic features. Destructuring is a prime example, but it's also important to understand the "gotchas".

Let's take a simple example to show you some of the power of destructuring.

view plain print about
1const nodes = [{
2    id: '0-1',
3    label: 'Led Zeppelin',
4    members: [{
5        id: '0-1-1',
6        label: 'Jimmy Paige'
7    }, {
8        id: '0-1-2',
9        label: 'Robert Plant'
10    }, {
11        id: '0-1-3',
12        label: 'John Paul Jones'
13    }, {
14        id: '0-1-4',
15        label: 'John Bonham'
16    }]
19const band = nodes[0];
20const { label: bandName, members } = band;
21const [leadGuitar, leadSinger, bassPlayer, drummer] = members;

So, what's it do? Let's break it down. I took the first node and assigned it to band. I then assigned the bandName and members variables from the band's label and members values, respectively. Then, I took the first four items from my members array, and assigned each of them to a variable as well. This offers you a lot of power, simplifies your code, and can save some CPU cycles as well.

But, what happens if something doesn't exist? Say you had a band with no members? (That's a trick), or members but no drummer? In those cases the members or drummer variables would be undefined.

Now, let's talk about "gotchas". Here's a neat bit of syntactic sugar for you.

view plain print about
1drummer = {...drummer, deceased: true};

Using the spread operator, with destructuring, we add a new key to the drummer object. But, wait...

We also replaced the drummer object. This is important. While using destructuring like this can be easy, and very effective, it can have consequences. If you needed to update drummer by reference, you just killed the reference assignment.

And, the above statement would error (as will the array example below). This is because we declared drummer (and members) using const. While we could adjust, add, or remove keys and values, we can't replace the variable. We would have to declare using let instead of const.

The same holds true when using a spread operator and destructuring when attempting to update an array.

view plain print about
1members = [...members, { id: '0-1-5', label: 'Jason Bonham' }];

While the members array now has a fifth item, the reference to band.members is no longer valid, as you replaced the variable.

But, this is no big deal, unless you needed to update the reference to the original variable. As long as you're aware of this limitation, it's easy to fallback on other methods to update those references. Let's change our variable declarations a little bit, and retool this code to work for us.

view plain print about
1const band = nodes[0];
2const { label: bandName, members } = band;
3let [leadGuitar, leadSinger, bassPlayer, drummer] = members;
5Object.assign(drummer, {deceased: true});
6members.splice(3, 0, {id: '0-1-5', label: 'Jason Bonham'}); // insert Jason in the drummer array position
7[,,,drummer] = members; // and update the declaration

We switched our member variable declarations to let, so they can be replaced, updated the drummer, inserted a new member in the correct position, and updated the drummer reference to the new member.

This post only briefly touches on the power of destructuring, in modern EcmaScript. For a fantastic overview, check out the MDN documentation.