In this article, you’ll learn some common data filtering techniques in JavaScript and their application in React. We’ll run through filter()
, map()
, and regular old for
loops, covering how each works, their strengths and limitations, and situations where you might want to use a specific method over another.
The most common way to filter data in JavaScript is to use the array’s filter()
method. It can be used on any array and, provided a filtering function, returns a new array containing only selected items. The functionality is similar to internet search, where you input a query and receive relevant results. Utilizing the search box, users can easily filter the data based on specific criteria.
To understand filter()
better, take a look at the following example:
1const input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2
3const isEven = (value) => value % 2 === 0;
4const isOdd = (value) => !isEven(value);
5const isPrime = (value) => {
6 for (let i = 2, s = Math.sqrt(value); i <= s; i++) {
7 if (value % i === 0) return false;
8 }
9
10 return value > 1;
11};
12
13const even = input.filter(isEven);
14const odd = input.filter(isOdd);
15const prime = input.filter(isPrime);
16
17console.log({ input, even, odd, prime });
18/* result:
19{
20 input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
21 even: [2, 4, 6, 8, 10],
22 odd: [1, 3, 5, 7, 9],
23 prime: [2, 3, 5, 7],
24}
25*/
26
A filtering function, like isEven()
or isOdd()
, receives a single element of an array and should return a Boolean value indicating whether to include the item in the filtered subset. Because filter()
will always run for every element of an array, it’s worth keeping the complexity of the filtering function in check. isPrime()
can serve as an example of a more complex function, in this case, with a nested loop.
The use of filter()
translates one-to-one to the React world. Given the filtering functions and general example above, you can create the following component where users can enter an active search query or filter method to narrow down the displayed data:
1// ...
2const App = () => {
3 const [input] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
4
5 return (
6 <div>
7 <div>Even: {input.filter(isEven).join(", ")}</div>
8 <div>Odd: {input.filter(isOdd).join(", ")}</div>
9 <div>Prime: {input.filter(isPrime).join(", ")}</div>
10 </div>
11 );
12};
13
The most important thing to remember when using filter()
inside a React component is the rendering process. In the example above, filter()
will be run on every re-render, which can be costly performance-wise. If the re-render is triggered by a state change unrelated to the filtered data, running filter()
again is pointless. In this case, the recommended approach is to use the useMemo()
hook:
1// ...
2const App = () => {
3 const [input] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
4 const even = useMemo(() => input.filter(isEven), [input]);
5 const odd = useMemo(() => input.filter(isOdd), [input]);
6 const prime = useMemo(() => input.filter(isPrime), [input]);
7
8 return (
9 <div>
10 <div>Even: {even.join(", ")}</div>
11 <div>Odd: {odd.join(", ")}</div>
12 <div>Prime: {prime.join(", ")}</div>
13 </div>
14 );
15};
16
Thanks to useMemo()
, the value is memoized and only recomputed when one of the dependencies provided in the second parameter changes. This ensures no unnecessary filter()
calls and, thus, better performance.
While an array item is usually enough to work with to create a basic filtering function, it’s worth noting that the filter()
function can take other parameters if you ever need them.
Apart from the current item (value
), the function receives the item’s index
and input array
in the given order. These values can be helpful in many scenarios, such as when filtering based on item position in the array or based on values of other relatively positioned items. This flexibility allows for more precise internet searches or tailored filtering using a search box for user input.
1const input = [1, 4, 2, 3, 5, 8, 10, 6, 9, 7];
2
3const isBiggerThanNextValue = (value, index, array) => {
4 const nextValue = array[index + 1];
5
6 if (nextValue) {
7 return value > nextValue;
8 }
9
10 return false;
11};
12const biggerThanNextValue = input.filter(isBiggerThanNextValue);
13
14console.log(JSON.stringify({ input, biggerThanNextValue }));
15/* result:
16{
17 input: [1, 4, 2, 3, 5, 8, 10, 6, 9, 7],
18 biggerThanNextValue: [4, 10, 9],
19}
20*/
21
While the filter()
method can do a lot on its own, you’ll often need some more processing to get the necessary data. Adding map()
can make for a powerful combination, allowing you to transform data based on user input.
The map()
method is probably the most popular solution for data mapping, wherein a new data set is generated from an existing one on an element-by-element basis. The map()
method takes a function, which, given the same parameters as the filtering function—value
, index
, and array
—returns a new element that will be included in the output data set.
1const input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2
3const multiplyByTwo = (value) => value * 2;
4const addNextValue = (value, index, array) => {
5 const nextValue = array[index + 1] || 0;
6
7 return value + nextValue;
8};
9
10const multipliedByTwo = input.map(addNextValue);
11const addedNextValue = input.map(multiplyByTwo);
12
13console.log({ input, multipliedByTwo, addedNextValue });
14/* result:
15{
16 input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
17 multipliedByTwo: [3, 5, 7, 9, 11, 13, 15, 17, 19, 10],
18 addedNextValue: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20],
19}
20*/
21
When combined with filter()
, map()
can be an excellent filtering utility. It can be used both as a preprocessor on the original array as well as a post-processor on the already-filtered data, leading to numerous use cases, like post-processing filtered data to strings for easier display or preprocessing a data set to a format that’s easier to filter.
1// ...
2const evenMultipliedByTwo = input.filter(isEven).map(multiplyByTwo);
3const oddAddedNextValue = input.map(addNextValue).filter(isOdd);
4
5console.log({ input, evenMultipliedByTwo, oddAddedNextValue });
6/* result:
7{
8 input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
9 evenMultipliedByTwo: [4, 8, 12, 16, 20],
10 oddAddedNextValue: [3, 5, 7, 9, 11, 13, 15, 17, 19],
11}
12 */
13
There’s a reason why map()
and filter()
are such a common sight in JS codebases, especially React ones. Because of their clean callback syntax, immutability, and ease of use, they’ve dominated React—especially in its current, more functional, Hook-centric form.
While various immutable array methods are performant enough for pretty much all use cases (especially in React’s functional world), there are a few scenarios in which an old, imperative for
loop might be a better choice. Let’s take a look at some of them.
Say that you want to filter multiple arrays simultaneously or, better yet, filter a single array to various outputs. In this case, especially if the data set is large, you can optimize the process by filtering all the data in a single for
or while
loop.
1const even = input.filter((value) => value % 2 === 0);
2const odd = input.filter((value) => value % 2 !== 0);
3const prime = input.filter((value) => {
4 for (let i = 2, s = Math.sqrt(value); i <= s; i++) {
5 if (value % i === 0) return false;
6 }
7
8 return value > 1;
9});
10
11// for alternative
12const even = [];
13const odd = [];
14const prime = [];
15
16loop: for (let i = 0; i < input.length; i++) {
17 const value = input[i];
18
19 if (value % 2 === 0) even.push(value);
20 else odd.push(value);
21
22 for (let j = 2, s = Math.sqrt(value); j <= s; j++) {
23 if (value % j === 0) continue loop;
24 }
25
26 if (value > 1) prime.push(value);
27}
28
Unfortunately, the additional performance comes at the cost of code readability. Thus, it’s recommended to use this technique only when necessary.
Another use case for a for
loop is to control the iteration process better. Unlike filter()
, map()
, and similar methods, the iteration of a for
loop can be controlled with control statements, like break
or continue
.
Having such control allows you to stop the iteration process early or skip unnecessary processing. Additionally, with labels, you can have the same level of control even for nested loops.
This use case was demonstrated in the previous code example, where the continue
statement was used with the loop
label to skip the next bit of code on a given condition:
1// …
2loop: for (let i = 0; i < input.length; i++) {
3 // …
4 for (let j = 2, s = Math.sqrt(value); j <= s; j++) {
5 if (value % j === 0) continue loop;
6 }
7
8 if (value > 1) prime.push(value);
9}
10
While the continue
statement in the filter()
callback can be substituted with an early return
, the break
statement cannot. Because of this, if you need to exit the entire iteration process early, using an imperative loop like for
is your best option.
It’s safe to say that an imperative loop won’t fit as nicely as an immutable method, like filter()
, into the declarative React world.
Consider the following example, taken from one of the previous snippets but with both filter()
and map()
replaced by a for…of
loop:
1const App = () => {
2 const [input] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
3 const data = useMemo(() => {
4 const even = [];
5 const odd = [];
6 const prime = [];
7
8 for (let i of input) {
9 if (isEven(i)) even.push(i);
10 if (isOdd(i)) odd.push(i);
11 if (isPrime(i)) prime.push(i);
12 }
13
14 return [
15 { label: "Even", values: even },
16 { label: "Odd", values: odd },
17 { label: "Prime", values: prime },
18 ];
19 }, [input]);
20 const elements = [];
21
22 for (let { label, values } of data) {
23 elements.push(
24 <div>
25 {label}: {values.join(", ")}
26 </div>
27 );
28 }
29
30 return <div>{elements}</div>;
31};
32
As you can see, not only is the example longer, but it can also be hard to grasp, and it might feel “off” if you’re already used to React. This is meant to demonstrate that, in React, for
and other loops should be used even more sparingly than in Vanilla JS and only in the scenarios described above.
In this post, you learned how to use filter()
, map()
, and imperative loops to filter your data. You’ve also learned how to use those methods together to optimize filtering in specific scenarios. Finally, you’ve seen how you can apply this knowledge in React with the useMemo()
hook.
Anything we missed? Let us know on Twitter.
Reader