Here’s a trick I often find helpful.
Bad array. Very, very bad.
You have an array of whatever:
const array = [{ stuff }, { moreStuff }, ...]
But hiding in that array are some unusable null
or undefined
values:
const array = [{ good }, null, { great }, undefined]
Those empty values might be a sneaky little gift from your API. Or you may have left them there yourself while validating the original data. Either way, you’ve got a problem.
Looping over null data
If you try to perform actions on every item in the array, you’ll run into errors when you hit those null
and undefined
items:
const newArray = array.map(item => {
// Of course this will work, wheeee...
const assumption = item.thing
})
// Oh noooo...
🚨 Cannot read property "thing" of undefined. 🚨
🚨 Cannot read property "thing" of null. 🚨
Illegal! Now you’re a criminal. Before you interact with an item, you need to make sure it exists.
Null checks?
You could confirm each item exists by performing a null
check before you use it:
const newArray = array.map(item => {
// Life has made me cautious.
if (!item) {
return item // Just forget it
}
// If we get this far, item exists.
const assumption = item.thing
})
Buuut, now your code is getting cluttered. And worse, those dangerous empty values will be passed along to newArray
. So when newArray
is used, another round of suspicious null
checks will be needed.
The truth and only the truth
Want something better?
Here’s my favourite way to quickly remove all empty items from an array:
const array = [{ good }, null, { great }, undefined]
const truthyArray = array.filter(Boolean)
// truthyArray = [{ good }, { great }]
The filter(Boolean)
step does the following:
- Passes each item in the array to the
Boolean()
object - The
Boolean()
object coerces each item totrue
orfalse
depending on whether it’s truthy or falsy - If the item is truthy, we keep it
Where did item go?
I love how concise filter(Boolean)
is, but it might look strange that we aren’t explicitly passing item
to Boolean
.
The main thing to know is that, in JavaScript, this:
array.filter(item => Boolean(item))
is exactly the same as this:
array.filter(Boolean)
The second version is just written in a “tacit” or “point-free” style. We don’t name each item and pass it into Boolean
, but JavaScript understands that Boolean
takes one argument, so it takes the argument filter()
exposes and passes it to Boolean for you.
If you find the first version easier to understand, use it! Writing readable code is more important than using fancy tricks.
Safer mapping
With our new tool, we can remove the null
checks from above and chain a filtering step instead:
const newArray = array.filter(Boolean).map(item => {
// Item is always truthy!
const assumption = item.thing
})
Now, our map
can focus on what it’s trying to do, and we’ve removed the empty values from our pipeline forever.
Hope that helps!
Glossary
-
Falsy values:
false
,0
,-0
,0n
,""
,null
,undefined
,NaN
- Truthy values: anything not in the falsy list
Links
- Filter | MDN web docs
- Boolean | MDN web docs
- Falsy | MDN web docs
- Truthy | MDN web docs
- Type coercion | MDN web docs
- Tacit programming | Wikipedia
- Chapter 2, Professor Frisby’s Mostly Adequate Guide to Functional Programming | Brian Lonsdorf
Originally published at michaeluloth.com
Top comments (1)
That is a really cool idea. I often forget about passing built-in functions and such as callbacks directly to these methods (and I don't think I thought of chaining array methods either for some reason yet).
Thanks for this!