.filter is a built-in array iteration method that takes a “predicate” that is called for each value and returns an array that matches that condition (the “truthy value”).
That statement contains a lot of information. Let’s go through each one.
-
“Built-in” just means it’s part of the language – you don’t need to add any libraries to access this functionality.
-
An “iterative method” is a function that takes a run on each item of the array. .map and.reduce are examples of iterative methods.
-
“Predicate” means the function accepted in.fiflter.
-
A “truthy value” is any value that evaluates to true when cast to a Boolean value. Almost all values are true, except: undefined, null, false, 0, NaN or “” (empty string).
Let’s take a look at the following example to see how.filter works.
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
That’s a lot of information. I want a burger now, so let’s filter out this array.
const isBurger = ({cuisine}) => cuisine === 'Burger';
const burgerJoints = restaurants.filter(isBurger);
Copy the code
IsBurger is a predicate, and burgerJoints is an array of new, which is a subset of restaurants. It’s worth noting that the restaurants array is constant.
Here are a simple example of two lists being rendered – a raw restaurant array, and a filtered array of burgerJoints.
See the Pen .filter – isBurger by Adam Giese (@AdamGiese) on CodePen.
No predicate
For each predicate, there is an opposite negation predicate.
A predicate is a function that returns a Boolean value. Since booleans are only true and false, this means that it is easy to “flip” the value of the predicate.
I’ve been eating hamburgers for hours, and now I’m hungry again. This time, I wanted to filter out Burger to try something new. One option is to write the new isNotBurger predicate from scratch.
const isBurger = ({cuisine}) => cuisine === 'Burger'; const isNotBurger = ({cuisine}) => cuisine ! == 'Burger';Copy the code
However, look at how similar the two predicates are. This is not DRY code. Another option is to call the isBurger predicate and flip 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 want some negative predicates? Since this is something we might often want to do, it’s probably a good idea to write negative functions.
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
You may have some questions.
What is.apply?
MDN:
The apply () method calls the function given this and provides the parameters as arrays (or array-like objects).
What are the arguments?
MDN:
Arguments objects are local variables available in all (non-arrow) functions. You can use arguments to refer to the function’s argument object within a function.
Why use the old function instead of the cooler arrow function?
In this case, using traditional functions is necessary because arguments objects are _ unique _ available on traditional functions.
Until August 20, 2018. As some critics have correctly pointed out, you can write \ negate \ with the arrow function using the rest argument.
Returns the predicate
As we saw with the negate function, functions can easily return a new function in JavaScript. This is useful for writing “predicates.” For example, let’s review our isBurger and isPizza predicates.
const isBurger = ({cuisine}) => cuisine === 'Burger';
const isPizza = ({cuisine}) => cuisine === 'Pizza';
Copy the code
These two predicates have the same logic; They differ only in comparison. Therefore, we can wrap the shared logic in the isCuisine function.
const isCuisine = comparison => ({cuisine}) => cuisine === comparison;
const isBurger = isCuisine('Burger');
const isPizza = isCuisine('Pizza');
Copy the code
Now, what if we want to start checking prices?
const isPrice = comparison => ({price}) => price === comparison;
const isCheap = isPrice('Cheap');
const isExpensive = isPrice('Expensive');
Copy the code
Now isCheap and isExpensive are both DRY. Don’t repeat Yourself, isPizza and isBurger are DRY, but isPrice and isCuisine can share their logic!
const isKeyEqualToValue = key => value => object => object[key] === value;
// these can be rewritten
const isCuisine = isKeyEqualToValue('cuisine');
const isPrice = isKeyEqualToValue('price');
// these don'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 feature. In one line, you can elegantly create third-order functions.
See how easy it is to create multiple filter lists from the original restaurant array?
See the Pen .filter – returning predicates by Adam Giese (@AdamGiese) on CodePen.
Write a predicate
We can now filter our array through burgers or cheap prices…… But what if you want cheap burgers? One option is to link the two filters together.
const cheapBurgers = restaurants.filter(isCheap).filter(isBurger);
Copy the code
Another option is to “combine” the two predicates into one.
const isCheapBurger = restaurant => isCheap(restaurant) && isBurger(restaurant);
const isCheapPizza = restaurant => isCheap(restaurant) && isPizza(restaurant);
Copy the code
Look at all the duplicate code. We can definitely wrap it up as a new feature!
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 = restaurants.filter(isCheapPizza);
Copy the code
What if you don’t have pizza or hamburgers?
const either = (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 want to contain more than two foods? This is not an extensible approach. There are two built-in array methods that come in handy here. .every and.some are predicate methods that also accept predicates. .every checks whether each member of the array passes a predicate, while.some checks whether any members of the array pass a predicate.
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 into 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 predicates and return a predicate.
Because all of these predicates can be easily created using higher-order functions, it is not difficult to create and apply them based on user interaction. Taking everything we’ve learned together, here is an example of an application that searches for restaurants by applying a button click based filter.
See the Pen .filter – dynamic filters by Adam Giese (@AdamGiese) on CodePen.
conclusion
Filters are an important part of JavaScript development. Whether you’re picking out error data from an API response or responding to a user interaction, you’ll want a subset of array values a million times. I hope this overview helps you manipulate predicates to write more readable and maintainable code.