Chaining Javascript filters recursively

in

I recently realize a POC with Vue.js. I was confronted to this problem: how to apply an unknown number of filter to a collection?

My previous POC works like this: when I click to a filter, this filter is automatically push in an array of filter. And printed datas depends on this array of filters.

example

Let’s start with the following data:

const heroes = [
  { name: 'Wolverine',      family: 'Marvel',    isEvil: false },
  { name: 'Deadpool',       family: 'Marvel',    isEvil: false },
  { name: 'Magneto',        family: 'Marvel',    isEvil: true  },
  { name: 'Charles Xavier', family: 'Marvel',    isEvil: false },
  { name: 'Batman',         family: 'DC Comics', isEvil: false },
  { name: 'Harley Quinn',   family: 'DC Comics', isEvil: true  },
  { name: 'Legolas',        family: 'Tolkien',   isEvil: false },
  { name: 'Gandalf',        family: 'Tolkien',   isEvil: false },
  { name: 'Saruman',        family: 'Tolkien',   isEvil: true  }
];

And the following filters:

// not DC Comics
const f1 = h => h.family !== 'DC Comics'
// no evil hero
const f2 = h => h.isEvil === false

I can now chain the filters like this:

heroes.filter(f1)
      .filter(f2)
      .filter(whatever)
      // ...

But what if I have to apply severals filters on the same bench of data? … like this:

const activeFilters = [f1, f2]

Looping inside an array can be a solution:

var filteredHeroes = heroes
for (let filterIndex in activeFilters) {
  filteredHeroes = filteredHeroes.filter(activeFilters[filterIndex])
}

But as I explain in my previous post: imperative programming is not very elegant.

Solution: using recursion

A nicer solution is to use a functional approach by using recursion:

function recursive_filter (data, arrayFilters, index = 0) {
  if (arrayFilters.length === 0) {
    return data
  }
  if (index === arrayFilters.length - 1) {
    return data.filter(arrayFilters[index])
  }
  return recursive_filter(data.filter(arrayFilters[index]), arrayFilters, (index + 1))
}
const filteredHeroes = recursive_filter(heroes, activeFilters)

Bad solution: extending native prototype

As explained in the MDN, it is not recommended to extend native object (Array in our case). So the following code will works despite the good practice violation.

// DON't DO IT!
Array.prototype.recursive_filter = function (arrayFilters, index = 0) {
  if (arrayFilters.length === 0) {
    return this
  }
  if (index === arrayFilters.length - 1) {
    return this.filter(arrayFilters[index])
  }
  return this.filter(arrayFilters[index]).recursive_filter(arrayFilters, (index + 1))
}

const filteredHeroes = heroes.recursive_filter(activeFilters)

About the author

For the last decade, Maxence Poutord has worked with a variety of web technologies. He is currently focused on front-end development. On his day to day job, he is working as a senior front-end engineer at VSware. He is also a frequent tech speaker and a mentor. As a new digital nomad, he is living where the WIFI and sun is 😎
Do you want to know more? Read this!

Read next: