tutorial // Jan 28, 2022
How to Use Filter to Dynamically Filter an Array in JavaScript
How to use the JavaScript Array.Filter method to selectively filter or remove items from an array.
Getting Started
For this tutorial, we're going to use CheatCode's full-stack JavaScript framework, Joystick. Joystick brings together a front-end UI framework with a Node.js back-end for building apps.
To begin, we'll want to install Joystick via NPM. Make sure you're using Node.js 16+ before installing to ensure compatibility (give this tutorial a read first if you need to learn how to install Node.js or run multiple versions on your computer):
Terminal
npm i -g @joystick.js/cli
This will install Joystick globally on your computer. Once installed, next, let's create a fresh project:
Terminal
joystick create app
After a few seconds, you will see a message logged out to cd
into your new project and run joystick start
:
Terminal
cd app && joystick start
After this, your app should be running and we're ready to get started.
Wiring up a UI
First, before we filter our array, we're going to set up a UI to contextualize our work. Our goal will be to create a list of music albums that we can filter based on each album's genre.
In the app that was just created for us when we ran joystick create app
, an example component was created for us at /ui/pages/index/index.js
. Let's open that up now and replace the existing contents with the skeleton for our filtering UI.
/ui/pages/index/index.js
import ui from '@joystick.js/ui';
const albums = [
{ id: 1, artist: 'Queens of the Stone Age', title: 'Songs for the Deaf', year: 2002, genre: 'rock' },
{ id: 2, artist: 'David Bazan', title: 'Havasu', year: 2022, genre: 'rock' },
{ id: 3, artist: 'Dwight Yoakam', title: 'This Time', year: 1993, genre: 'country' },
{ id: 4, artist: 'Sion', title: 'Sion', year: 2021, genre: 'metal' },
{ id: 5, artist: 'Every Time I Die', title: 'Low Teens', year: 2016, genre: 'metal' },
{ id: 6, artist: 'Cannonball Adderley', title: 'Somethin\' Else', year: 1958, genre: 'jazz' },
{ id: 7, artist: 'The Bad Plus', title: 'Suspicious Activity?', year: 2005, genre: 'jazz' },
{ id: 8, artist: 'Cory Hale', title: 'Soft', year: 2020, genre: 'electronic' },
{ id: 9, artist: 'Rezz', title: 'Spiral', year: 2021, genre: 'electronic' },
{ id: 10, artist: 'Autolux', title: 'Future Perfect', year: 2004, genre: 'experimental' },
];
const Index = ui.component({
state: {
filteredAlbums: albums,
},
render: ({ each, state }) => {
return `
<div>
<ul>
${each(state.filteredAlbums, (album) => `
<li>${album.artist} - ${album.title} (${album.year}) <span>#${album.genre.toLowerCase()}</span></li>
`)}
</ul>
</div>
`;
},
});
export default Index;
Here, we're writing a component using the @joystick.js/ui
library that's a part of the Joystick framework. Components are functions which return a string of HTML that gets rendered in the user's browser, updating whenever dynamic data inside the component changes.
To get started, we're doing two big things here: just beneath our import of the ui
object from the @joystick.js/ui
package, we're defining an array of objects with each object representing an album in our list. We've created this here as we need to be able to reference an unmodified copy of our list (this will make more sense in a bit).
To the state
property on the options object we pass to our ui.component()
definition, we're setting a property filteredAlbums
to the albums
array. This is setting the default value for the filteredAlbums
property on state. State is temporary data inside of our component that only exists until the page refreshes.
Moving down to the render
function, we're return
ing a string of HTML which is rendering a <ul></ul>
(unordered list) tag which will display our list of albums. To list those items—using JavaScript destructuring on the first argument passed to the render
function—we pluck off the each
and state
properties from our component instance (this instance is the first argument passed to the render
function).
Inside of the HTML for our <ul></ul>
tag, we're using JavaScript interpolation to say "at this point in the string, inject this value." The value we want to inject is the result of calling the each()
function we just plucked off the component instance (it returns a string of HTML itself).
To that each
function, we pass an array of items, in this case our filteredAlbums
value from state
along with a function that will be called for each item in the array. That function is expected to return a string of HTML for each value in the array. Here, each item in the array is an album
and we want to return an <li></li>
(list item) tag for each album.
Inside of that function, we return a string identical to the main return
for our render
function, passing our <li></li>
tag populated with the parts of our album—using the same JavaScript interpolation tag ${}
we just learned about to inject values into the string. The end result of this code running will look something like this:
<ul>
<li>Queens of the Stone Age - Songs for the Deaf (2002) <span>#rock</span></li>
<li>David Bazan - Havasu (2022) <span>#rock</span></li>
<li>Dwight Yoakam - This Time (1993) <span>#country</span></li>
...
</ul>
Now that we have our list rendering, next, we want to start wiring up our filtering. To do it, first, we need to have a mechanism by which we'll actually filter our list.
Wiring up an array to filter
Like we hinted at earlier, our goal is to filter our albums array by genre. To kick off that process, now, we're going to add a custom method function that retrieves the genre from each of our albums and populate that into a <select></select>
tag that we'll use to filter our list.
/ui/pages/index/index.js
import ui from '@joystick.js/ui';
const albums = [
{ id: 1, artist: 'Queens of the Stone Age', title: 'Songs for the Deaf', year: 2002, genre: 'rock' },
{ id: 2, artist: 'David Bazan', title: 'Havasu', year: 2022, genre: 'rock' },
{ id: 3, artist: 'Dwight Yoakam', title: 'This Time', year: 1993, genre: 'country' },
{ id: 4, artist: 'Sion', title: 'Sion', year: 2021, genre: 'metal' },
{ id: 5, artist: 'Every Time I Die', title: 'Low Teens', year: 2016, genre: 'metal' },
{ id: 6, artist: 'Cannonball Adderley', title: 'Somethin\' Else', year: 1958, genre: 'jazz' },
{ id: 7, artist: 'The Bad Plus', title: 'Suspicious Activity?', year: 2005, genre: 'jazz' },
{ id: 8, artist: 'Cory Hale', title: 'Soft', year: 2020, genre: 'electronic' },
{ id: 9, artist: 'Rezz', title: 'Spiral', year: 2021, genre: 'electronic' },
{ id: 10, artist: 'Autolux', title: 'Future Perfect', year: 2004, genre: 'experimental' },
];
const Index = ui.component({
state: {
filteredAlbums: albums,
},
methods: {
getAlbumGenres: () => {
const genres = albums.map(({ genre }) => {
const capitalizedGenre = genre.charAt(0).toUpperCase() + genre.slice(1);
return capitalizedGenre;
});
return Array.from(new Set(genres));
},
},
render: ({ each, state, methods }) => {
return `
<div>
<select>
<option value="all">All</option>
${each(methods.getAlbumGenres(), (genre) => {
return `
<option value="${genre.toLowerCase()}">${genre}</option>
`;
})}
</select>
<ul>
${each(state.filteredAlbums, (album) => `
<li>${album.artist} - ${album.title} (${album.year}) <span>#${album.genre.toLowerCase()}</span></li>
`)}
</ul>
</div>
`;
},
});
export default Index;
Focusing on the methods
property that we added to the options we're passing to ui.component()
, here, we've added a method (a miscellaneous function you can call on a Joystick component) called getAlbumGenres
.
When we call it, we create a variable called genres
which is set to a JavaScript .map()
that uses JavaScript destructuring to pluck off the genre
property from each object in our albums
array at the top of our file. Inside of the map, we capitalize the name of the genre (in our albums
array it's lowercase) for display in our UI.
Not familiar with the JavaScript Array.Map function? Give this tutorial a read.
Once we have capitalizedGenre
, we return it from the .map()
. This should get us something like this:
['Rock', 'Rock', 'Country', 'Metal', 'Metal', 'Jazz', 'Jazz', 'Electronic', 'Electronic', 'Experimental']
This gets us part of the way there, but there's an obvious problem: we have a lot of duplicates. To get around this, after we have our array in the genres
variable, from our getAlbumGenres()
function we use the new Set()
class constructor, passing in our genres
variable. This will give us a JavaScript Set in return which is an object only containing unique values, like this:
{ 'Rock', 'Country', 'Metal', 'Jazz', 'Electronic', 'Experimental' }
Though it may not look like a traditional JavaScript object (a set of key/value pairs), JavaScript recognizes it as such (a Set is just a special type of an Object, extended from the main Object prototype in the language). While this gets us one step closer, because we need to be able to loop over this list, we take the result of calling new Set(genres)
and pass it directly to Array.from()
to get back our unique list as an array:
['Rock', 'Country', 'Metal', 'Jazz', 'Electronic', 'Experimental']
Focusing back on the render
function, we can see that we've added in the <select></select>
tag we hinted at above. Inside, we add an <option></option>
tag with a value of all
and text content of "All" (this will allow us to move back to the full list from a filtered list).
Just beneath this, we make use of the each()
function again along with JavaScript interpolation to loop over the result of calling the methods.getAlbumGenres()
function we just wrote (notice we've added methods
as one of the values we're plucking off the component instance passed to the render
function).
For each genre
, we output an <option></option>
tag with the lowercased version of the genre
for the value
attribute and the capitalized genre
for the text content.
Almost done. Now, we're ready to filter our list. To do it, we're going to add an event listener on the <select></select>
tag we just added to our render
:
/ui/pages/index/index.js
import ui from '@joystick.js/ui';
const albums = [ ... ];
const Index = ui.component({
state: {
filteredAlbums: albums,
},
methods: { ... },
events: {
'change select': (event, component) => {
const filterByGenre = event.target.value;
component.setState({
filteredAlbums: filterByGenre === 'all' ? albums : albums.filter((album) => {
return album.genre === filterByGenre;
})
});
},
},
render: ({ each, state, methods }) => {
return `
<div>
<select>
<option value="all">All</option>
${each(methods.getAlbumGenres(), (genre) => {
return `
<option value="${genre.toLowerCase()}">${genre}</option>
`;
})}
</select>
<ul>
${each(state.filteredAlbums, (album) => `
<li>${album.artist} - ${album.title} (${album.year}) <span>#${album.genre.toLowerCase()}</span></li>
`)}
</ul>
</div>
`;
},
});
export default Index;
Time for the important part. Here, we've added an events
option to our ui.component()
and on it, we've defined an event listener which says "when the change
event is detected on any select
element in the component, call the function being assigned here."
Inside of that function, for the first argument we receive the DOM event object that's created when the change
event occurs and the component
instance as the second argument.
Before we perform our filter, first, we make sure to grab the genre we're trying to filter by—this will be the value
property of the <select></select>
or the event.target
where the event originated from—and store it in the filterByGenre
variable.
Next, we make a call to component.setState()
passing an object of properties that we want to modify on our component's state
, in this case, filteredAlbums
. What we set filteredAlbums
to depends on the value of filterByGenre
. If filterByGenre
is set to all
then we do not want to filter our list. Here, we use a JavaScript ternary operator to say, if the value is 'all'
, return the untouched albums
array. Otherwise, or, else :
we want to call the .filter()
method on the albums
array.
To albums.filter()
we pass a function that's called for each item in the array. If the value returned by that function is a Boolean true
, the item will be kept in the array. If the value returned is a Boolean false
, it will be filtered out of the array. Here, to get that true
or false
value, we check to see if the .genre
property of the album we're currently looping over matches filterByGenre
. If it does, it's a keeper. If it doesn't, we toss it.
Because we're passing our call to albums.filter()
directly to filteredAlbums
on the object we pass to component.setState()
, assuming our user hasn't selected the all
option, we will update filteredAlbums
on state to only contain an array of objects where the genre
property on each object matches what was selected in the <select></select>
list of genres.
In a Joystick component, changes to state
trigger a re-render, meaning our render()
function will be called again immediately after our call to component.setState()
, passing in the new state.filteredAlbums
value.
That's it! Now, if we look in the browser, we should be able to filter our list as expected:
Wrapping up
In this tutorial, we learned how to filter an array using the JavaScript Array.Filter method. To contextualize our work, we created a user interface using the @joystick.js/ui
library from the Joystick framework to help us render a list of albums to filter, along with a select input we could use to filter that list by genre.