Originally published in the CSS-Tricks article Level Up Your. Filter Game.

.filter is a built-in iteration method that takes an assertion function that is called on each array member of the iteration, filtering out (that is, keeping) the member if the function returns a true value, and filtering out (false) the member otherwise. Finally,.filter returns a subset of the original array.

There are a lot of concepts to explain in this paragraph! Let’s look at each of them.

  • “Built-in” means that the presentation is part of the language — you don’t need to add any libraries to use this function.

  • An “iteration method” is a function that is used on each array member of the iteration. Other iterative methods include.map and.reduce.

  • An assertion is a function that returns a Boolean value.

  • A “truth value” is a value that results in true when converted to a Boolean value. Almost all values are true except undefined, null, false, 0, NaN, and “” (empty string).

Let’s start with.filter. First we have an array variable that contains a list of restaurants.

const restaurants = [
    {
        name: "Dan's Hamburgers".price: 'Cheap'.cuisine: 'Burger'}, {name: "Austin's Pizza".price: 'Cheap'.cuisine: 'Pizza'}, {name: "Via 313".price: 'Moderate'.cuisine: 'Pizza'}, {name: "Bufalina".price: 'Expensive'.cuisine: 'Pizza'}, {name: "P. Terry's".price: 'Cheap'.cuisine: 'Burger'}, {name: "Hopdoddy".price: 'Expensive'.cuisine: 'Burger'}, {name: "Whataburger".price: 'Moderate'.cuisine: 'Burger'}, {name: "Chuy's".cuisine: 'Tex-Mex'.price: 'Moderate'}, {name: "Taquerias Arandina".cuisine: 'Tex-Mex'.price: 'Cheap'}, {name: "El Alma".cuisine: 'Tex-Mex'.price: 'Expensive'}, {name: "Maudie's".cuisine: 'Tex-Mex'.price: 'Moderate',},];Copy the code

There’s a lot of information in there, and now I want a hamburger, so let’s filter it out of this array.

const isBurger = ({cuisine}) = > cuisine === 'Burger';
const burgerJoints =  restaurants.filter(isBurger);
Copy the code

IsBurger joints is our assert function, and joints are a new array of subsets derived from restaurants. The important thing to note here is that the restaurants array itself does not change when the.filter method is executed.

For Codepen notes, burgerJoints are arrays that pass through.

No assertion

For every assertion, there is a corresponding negative assertion.

Assertions are functions that return Boolean values. Because there are only two possible Boolean values, this means it is easy to “flip” the asserted value.

A few hours later, I’m hungry. I’ve already had a hamburger. Now I want something else, as long as it’s not a hamburger. One option is to write an isNotBurger assertion from scratch.

const isBurger = ({cuisine}) = > cuisine === 'Burger';
const isNotBurger = ({cuisine}) = >cuisine ! = ='Burger';
Copy the code

But it seems silly that the assertions are so similar that we wrote duplicate code and were not DRY enough. The other way is to call the isBurger assertion and simply reverse the result.

const isBurger = ({cuisine}) = > cuisine === 'Burger';
const isNotBurger = restaurant= >! isBurger(restaurant);Copy the code

This is even better! If the definition of a hamburger changes, you only need to change the logic in one place. But what if we need several assertions that we want to negate at the same time? Since this is probably something you do all the time, you can write a more generic negate function.

const negate = predicate= > function () {
  return! predicate.apply(null.arguments);
}

const isBurger = ({cuisine}) = > cuisine === 'Burger';
const isNotBurger = negate(isBurger);

const isPizza = ({cuisine}) = > cuisine === 'Pizza';
const isNotPizza = negate(isPizza);
Copy the code

Now, there might be some questions in your head:

. What is the apply?

MDN:

Apply () calls the function with the given this value and the array (or array-like object) argument arguments.

Arguments?

MDN:

Arguments are local variables provided by all functions except the arrow functions. You can use the arguments object inside a function to refer to the list of arguments passed to the function when it is called.

Why the old function form instead of the new cooler arrow function?

In this case, returning the traditional function function is necessary because the argument object arguments _ _ is only available in traditional functions.

Of course, you can do this (write the return function as an arrow function, and use the residual argument operator to receive arguments).

const negate = predicate= >(... args) => ! predicate(... args)Copy the code

Returns the assertion

As we saw in the negate function, a function can easily return a new function in JavaScript. This is useful for writing an assertion creator. Let’s review the isBurger and isPizza claims.

const isBurger = ({cuisine}) = > cuisine === 'Burger';
const isPizza = ({cuisine}) = > cuisine === 'Pizza';
Copy the code

These two assertions are not negative of each other, but have the same logic of judgment, differing only in the value of the comparison. So we can combine these two functions into an isCuisine function:

const isCuisine = comparision= > ({cuisine}) => cuisine === comparision;
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');
Copy the code

This is very good! Now, what if we need to filter prices?

const isPrice = comparision= > ({price}) => price === comparision;
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');
Copy the code

Now isCheap and isExpensive are DRY, isPazza and isBurger are DRY — but isPrice and isCuisine have repetitive logic code! Fortunately, we can take it a step further.

const isKeyEqualToValue = key= > value => object= > object[key] === value;

// These can be rewritten
const isCuisine = isKeyEqualToValue('cuisine');
const isPrice = isKeyEqualToValue('price');

// This doesn't need to change
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');
Copy the code

To me, that’s the beauty of the arrow function. In one line, you can elegantly create a third-order function. IsKeyEqualToValue is a function that returns isPrice, and it is also a function that returns isCheap.

See how easy it is to create multiple filter lists from the original restaurants array.

Combined assertions

Now we can filter out restaurants with burgers or cheap prices, but what if we want to filter out burger restaurants with cheap prices? One option is to put two.filters together.

const cheapBurgers = restaurants.filter(isCheap).filter(isBurger);
Copy the code

Another is to “combine” two assertions into one:

const isCheapBurger = restaurant= > isCheap(restaurant) && isBurger(restaurant);
const isCheapPizza = restaurant= >IsCheap (restaurant) && isPizza (restaurant);Copy the code

Look at all this repetitive code. We can wrap it as a new function!

const both = (predicate1, predicate2) = > value => (predicate1(value) && predicate2(value);

const isCheapBurger = both(isCheap, isBurger);
const isCheapPizza = both(isCheap, isPizza);

const cheapBurgers = restaurants.filter(isCheapBurger);
const cheapPizza = restautants.filter(isCheapPizza);
Copy the code

What if you want pizza or a burger?

const both = (predicate1, predicate2) = > value => (predicate1(value) || predicate2(value);

const isDelicious = either(isBurger, isPizza);
const deliciousFood = restaurants.filter(isDelicious);
Copy the code

It’s a step in the right direction, but what if you have more than two foods you want to include? This is not a scalable method. Two built-in array methods,.every and.some, are useful here, both of which accept assertion functions. .every checks if _ every _ member can pass an assertion, while.some checks if any _ and _ array members can pass an assertion.

const isDelicious = restaurant= > [isPizza, isBurger, isBbq].some(predicate= > predicate(restaurant));
const isCheapAndDelicious = restaurant= > [isDelicious, isCheap].every(predicate= > predicate(restaurant));
Copy the code

And, as always, let’s wrap them up in some useful abstractions.

const isEvery = predicates= > value => predicates.every(predicate= > predicate(value));
const isAny = predicates= > value => predicates.some(predicate= > predicate(value));

const isDelicious = isAny([isBurger, isPizza, isBbq]);
const isCheapAndDelicious = isEvery([isCheap, isDelicious]);
Copy the code

Both isEvery and isAny take an array of assertions and return an assertion function.

Because all of these assertions are easily created by higher-order functions, it is not difficult to create and apply them based on user interactions. For all we’ve learned, this is an example of an application that searches for restaurants based on button clicks.

conclusion

Filters are an important part of JavaScript development. There are many times when you need to conditionally obtain a subset of an array, whether to retrieve data from an API response or to respond to a user interaction. I hope this article has helped you understand the.filter function and use assertions to write more readable and maintainable code.

(after)