JavaScript array methods are super useful, and learning how to use them can really help improve the readability of your code. This is the third and final part of this series, and today I'll be discussing the filter
function and how you can use it to make your code more concise. It's probably the easiest of the three to understand, since it's fairly straightforward, but it's always helpful to have examples on hand to understand exactly how it does and doesn't work.
Psst — don't have time to read through the entire article? Follow me on Instagram to get short, digestible posts on all things web development.
The filter
function is called on an array and — like map
— takes in one parameter: a callback. It performs this callback function on every element in the array, and includes the element in a new array if the callback returns true. Now, you might think that you can replicate this functionality without too much effort using map
, but the key difference here is that map
always returns an array with the same length as the original. (Well, almost always, but that's a bit more complicated and not really in the spirit of map
to begin with.) On the other hand, filter
will create an array that's only as large as the number of elements that pass the callback.
That may seem like a lot, but it honestly works just like you'd expect to. Let's take a look at a simple example:
A trivial example
Here, we add filter the array to only include all elements greater than 2:
const arr = [1,2,3,4,5];
const newarr = arr.filter(el => el > 2);
console.log(newarr); // => [3,4,5]
The important thing to note here is that newarr
is only of length 3; it doesn't return a null
or undefined
for the first two elements — it simply omits them entirely. We'll use this property to our advantage in the very next example.
Deleting caches (but properly this time)
If you've read my article on the map function, you'll recognize this example. However, it can still be improved: the null
values that we returned could end up throwing an error later down the line. To avoid that, we're going to use filter
to return an array that consists only of the elements that match the condition.
This is what it looks like:
self.addEventListener('activate', e => {
// Delete all caches that aren't named currentCache.
e.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(cacheNames.filter(cacheName => {
return cacheName !== currentCache;
}).map(cacheName => {
console.log('Deleting out of date cache:', cacheName);
return caches.delete(cacheName);
}));
})
);
});
Again, very similar to the previous example. The key difference here is that before we map each cache to its delete
function, we're filtering out all the caches that don't match our condition. That way, the currentCache
won't appear in the array being passed to the map
and thus we avoid any null
values.
Let's look another example:
Filtering posts in a blog
On my blog page, I use a tagging system to allow users to filter content and view only the posts they're interested in. To do this, I actually created a JavaScript library called dynamic-tags. While creating it, I needed to find a way to take all of the posts on the page and only display the ones that matched the given filter.
While this could have devolved into a nasty mess of for
loops and if
statements, I used the filter
function to make things both readable and efficient. Here's how it ended up:
function filterPosts() {
activePosts = posts;
tagFilter.forEach(tag => {
activePosts = activePosts.filter(post => tagDict[tag].includes(post));
});
}
Effectively three lines of code do much of the heavy lifting. In it, we go through each selected tag and call filter
on activePosts
to progressively remove any post that doesn't contain that tag. Even though filter
might seem like a very minor abstraction over a plain for
loop, we can see in this example just how concise and readable it makes your code.
Let's take a look at one final example:
Making things searchable
As an extension to the library I created above, I also wanted to create a system where users could search for topics and filter posts based on that. This is still a work in progress, but it already includes several useful examples of the filter
function in action.
Let's take a look at my favorite one:
populateSearchBox(el) {
let searches = this.allTags.filter(tag => tag.toLowerCase().includes(el.childNodes[0].data.toLowerCase()));
let searchBox = this.filter.querySelector(this.classListToSelector(this.searchBoxClass));
if (searchBox) {
searchBox = "";
searches.forEach(search => {
if (!this.tagFilter.includes(search)) this.addTagTo(search, searchBox, false);
});
}
}
In this snippet, instead of filtering out posts, we're actually filtering out suggestions in the search box. Here, the filter
function is used to ensure we're only appending suggestions that haven't already been selected. That is, we don't want a user to be filtering by topics that they've already filtered out.
The actual filter
function is quite interesting — it looks for all the tags that contain the phrase inside the search bar. For example, given the tags ['May', 'June', 'July']
, typing a 'y' into the search bar should return ['May', 'July']
. On the other hand, typing 'J' should return ['June', 'July']
.
Wrapping it up
And that's a wrap! I hope these examples gave you an idea of how the filter
function is used in production, and how it can help make code more readable and versatile. Let me know in the comments if you've used the filter
function in a cool way, and make sure to read the other two posts in the series if you haven't already!
As always, don't forget to follow me for more content like this. I'm currently writing on dev.to and Medium, and your support on either platform would be very much appreciated. I also have a membership set up, where you can get early previews of articles and exclusive access to a whole bunch of resources. Also, if you've particularly enjoyed this post, consider supporting me by buying me a coffee. Until next time!