NEW!

Joystick: The Full-Stack JavaScript Framework Learn More

How to Use Filter to Dynamically Filter an Array in JavaScript

What You Will Learn in This Tutorial

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 returning 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.

Get the latest free JavaScript and Node.js tutorials, course announcements, and updates from CheatCode in your inbox.

No spam. Just new tutorials, course announcements, and updates from CheatCode.

Questions & Comments

Cart

Your cart is empty!

  • Subtotal

    $0.00

  • Total

    $0.00