Filter - Fewer is Better
When you need to eliminate entries from an array, you are filtering.
Method | Returns | One-for-one | Runs for All |
---|---|---|---|
.filter((value, index, array) => Boolean) |
Array |
No | Yes |
The .filter()
function takes a predicate β a function that returns true or false. In .map()
we get the return value of the mapper function; with .filter()
we use the return value of the predicate to determine if the value will appear in the new result array.
Code Comparison
To get a sense for what .filter()
does, let's take a look at other ways you might filter an array.
const data = [1, 2, undefined, 3, 'e'];
// Older Imperative way - for loop
function justNumbers(data) {
const newData = [];
for (let i = 0; i < data.length; i += 1) {
if (typeof data[i] === 'number') {
newData.push(data[i]);
}
}
return newData;
}
// Newer Imperative way - for..of loop
function justNumbers(data) {
const newData = [];
for (let value of data) {
if (typeof value === 'number') {
newData.push(value);
}
}
return newData;
}
// New Declarative way - .filter()
const justNumbers = (data) => data
.filter(value => typeof value === 'number');
Use Cases
I strongly recommend pasting some of these examples into a browser console, a code sandbox website, or a playground like RunJS so you can try different changes to see what happens.
Removing Empty/Falsy Entries
If you have an array with undefined
or null
or other falsy values that you want to remove, you can use the Boolean type function, which will return a Boolean from the input value.
const data = [ { id: 1 }, undefined, { id: 3 }, undefined ];
data.filter(Boolean);
// [ { id: 1 }, { id: 3 } ]
Using .filter(Boolean)
may seem odd when most examples use anonymous functions as the predicate, but remember any function can be passed to the array methods.
const makeBoolean = (value) => !!value;
// These two calls to filter work the same
data.filter(makeBoolean);
data.filter((value) => !!value);
If instead you need to remove nullish values but keep falsy values like 0
or ''
, this simple condition falls down.
const data = [ 0, undefined, true, false ];
data.filter(Boolean);
// [ true ]
We'll need something a bit different for nullish checks
const data = [ 0, undefined, true, false ];
data.filter((value) => value != null);
// [ 0, true, false ]
These two options are very common for cleaning up invalid data.
Removing Duplicates
Sometimes you get more data than you want, even the same data multiple times.
Because the filter predicate receives the value
, the index
and the original array
, we can use these elements to check for other matches outside our current record with different indexes.
const data = [ 1, 2, 4, 2, 5, 3, 1 ];
data.filter((value, index, array) => {
// We can used indexOf for primitive values
// True if the first match is also our index
return array.indexOf(value) === index;
});
// [ 1, 2, 4, 5, 3 ]
For complex objects, we can use other array methods, .find()
or .findIndex()
.
const data = [
{ id: 1, label: 'pear' },
{ id: 2, label: 'banana' },
{ id: 1, label: 'pear' },
{ id: 3, label: 'apple' },
];
// Similar index check as with the primitive value filter
data.filter((value, index, array) => {
// findIndex uses a predicate similar to filter
return array.findIndex((entry) => entry.id === value.id) === index;
});
// Using .find() works...most of the time
data.filter((value, index, array) => {
// This time we see if the found object is the same object
return array.find((entry) => entry.id === value.id) === value;
});
I generally recommend against the .find()
version. It can be a little harder to follow because we're matching objects rather than indices, so when looking at the data it can be unclear if two elements are the same object or just contain the same data. This uncertainty can create unexpected risk: if one object appears multiple times, it may not filter correctly. Try the two filter actions above with the data below to see the difference.
const pear = { id: 1, label: 'pear' };
const banana = { id: 2, label: 'banana' };
const apple = { id: 3, label: 'apple' };
// The same object is used twice
const data = [ pear, banana, pear, apple ];
Reject
Now that we have some examples of matching predicates, sometimes we want the exact opposite; we want to reject the matches.
Sure we could write the opposite predicate filter function, but if we're inside a library or utility that accepts a filter function rather than writing it inline, being able to "do the opposite" can be convenient.
Underscore or Lo-dash provide this to simplify your life, but implementing this is fairly straightforward.
Let's take the example from Removing Duplicates, and see what we need to do to invert the result. First, let's isolate the predicate.
const data = [ 1, 2, 4, 2, 5, 3, 1 ];
// We can used indexOf for primitive values
// True if the first match is also our index
const getFirstInstance = (value, index, array) => {
return array.indexOf(value) === index;
};
// Now, use the defined function as a regular filter
data.filter(getFirstInstance);
// [ 1, 2, 4, 5, 3 ]
Now that we have the predicate, let's see how we can invert the response.
const data = [ 1, 2, 4, 2, 5, 3, 1 ];
const getFirstInstance = (value, index, array) => {
return array.indexOf(value) === index;
};
// An explicit wrapper to invert the predicate result
data.filter((value, index, array) => !getFirstInstance(value, index, array));
// [ 2, 1 ]
We can use a little functional programming style to simplify this into a small higher order function called: not
.
const not = (predicate) => (...args) => !predicate(...args);
Let's put it together and see "reject"
const data = [ 1, 2, 4, 2, 5, 3, 1 ];
const not = (predicate) => (...args) => !predicate(...args);
const getFirstInstance = (value, index, array) => {
return array.indexOf(value) === index;
};
// Use an HOF to invert the predicate
data.filter(not(getFirstInstance));
// [ 2, 1 ]
Duplicate Performance
These examples are not very efficient in how they iterate over the array. As the size of your data increases, you may experience performance concerns if you are looping over the array once for each value. I don't recommend worrying about it right away, but it is something to keep in mind. There are utilities that substantially improve on this simple filter concept, should you need them.
Summary
Filter allows you to extract or eliminate specific entries from your array of data. Whether your are filtering for undefined
or specific primitive values or reaching into nested objects to check values or even run calculations, the predicate passed to .filter()
can be a very powerful tool.
Top comments (0)