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.

Data filtering

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:

const input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const isEven = (value) => value % 2 === 0;
const isOdd = (value) => !isEven(value);
const isPrime = (value) => {
  for (let i = 2, s = Math.sqrt(value); i <= s; i++) {
    if (value % i === 0) return false;
  }

  return value > 1;
};

const even = input.filter(isEven);
const odd = input.filter(isOdd);
const prime = input.filter(isPrime);

console.log({ input, even, odd, prime });
/* result:
{
  input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  even: [2, 4, 6, 8, 10],
  odd: [1, 3, 5, 7, 9],
  prime: [2, 3, 5, 7],
}
*/

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.

Filtering in React

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:

// ...
const App = () => {
  const [input] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

  return (
    <div>
      <div>Even: {input.filter(isEven).join(", ")}</div>
      <div>Odd: {input.filter(isOdd).join(", ")}</div>
      <div>Prime: {input.filter(isPrime).join(", ")}</div>
    </div>
  );
};

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:

// ...
const App = () => {
  const [input] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  const even = useMemo(() => input.filter(isEven), [input]);
  const odd = useMemo(() => input.filter(isOdd), [input]);
  const prime = useMemo(() => input.filter(isPrime), [input]);

  return (
    <div>
      <div>Even: {even.join(", ")}</div>
      <div>Odd: {odd.join(", ")}</div>
      <div>Prime: {prime.join(", ")}</div>
    </div>
  );
};

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.

Using other parameters

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.

const input = [1, 4, 2, 3, 5, 8, 10, 6, 9, 7];

const isBiggerThanNextValue = (value, index, array) => {
  const nextValue = array[index + 1];

  if (nextValue) {
    return value > nextValue;
  }

  return false;
};
const biggerThanNextValue = input.filter(isBiggerThanNextValue);

console.log(JSON.stringify({ input, biggerThanNextValue }));
/* result:
{
  input: [1, 4, 2, 3, 5, 8, 10, 6, 9, 7],
  biggerThanNextValue: [4, 10, 9],
}
*/

Mapping: also sometimes a filter

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.

Data mapping

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.

const input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const multiplyByTwo = (value) => value * 2;
const addNextValue = (value, index, array) => {
  const nextValue = array[index + 1] || 0;

  return value + nextValue;
};

const multipliedByTwo = input.map(addNextValue);
const addedNextValue = input.map(multiplyByTwo);

console.log({ input, multipliedByTwo, addedNextValue });
/* result:
{
  input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  multipliedByTwo: [3, 5, 7, 9, 11, 13, 15, 17, 19, 10],
  addedNextValue: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20],
}
*/

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.

// ...
const evenMultipliedByTwo = input.filter(isEven).map(multiplyByTwo);
const oddAddedNextValue = input.map(addNextValue).filter(isOdd);

console.log({ input, evenMultipliedByTwo, oddAddedNextValue });
/* result:
{
  input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  evenMultipliedByTwo: [4, 8, 12, 16, 20],
  oddAddedNextValue: [3, 5, 7, 9, 11, 13, 15, 17, 19],
}
 */

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.

Ol’ reliable—using for loops for filtering

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.

Combining filtering operations

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.

const even = input.filter((value) => value % 2 === 0);
const odd = input.filter((value) => value % 2 !== 0);
const prime = input.filter((value) => {
  for (let i = 2, s = Math.sqrt(value); i <= s; i++) {
    if (value % i === 0) return false;
  }

  return value > 1;
});

// for alternative
const even = [];
const odd = [];
const prime = [];

loop: for (let i = 0; i < input.length; i++) {
  const value = input[i];

  if (value % 2 === 0) even.push(value);
  else odd.push(value);

  for (let j = 2, s = Math.sqrt(value); j <= s; j++) {
    if (value % j === 0) continue loop;
  }

  if (value > 1) prime.push(value);
}

Unfortunately, the additional performance comes at the cost of code readability. Thus, it’s recommended to use this technique only when necessary.

Reducing processing time with control statements

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:

// …
loop: for (let i = 0; i < input.length; i++) {
  // …
  for (let j = 2, s = Math.sqrt(value); j <= s; j++) {
    if (value % j === 0) continue loop;
  }

  if (value > 1) prime.push(value);
}

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.

Using loops in React

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:

const App = () => {
  const [input] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  const data = useMemo(() => {
    const even = [];
    const odd = [];
    const prime = [];

    for (let i of input) {
      if (isEven(i)) even.push(i);
      if (isOdd(i)) odd.push(i);
      if (isPrime(i)) prime.push(i);
    }

    return [
      { label: "Even", values: even },
      { label: "Odd", values: odd },
      { label: "Prime", values: prime },
    ];
  }, [input]);
  const elements = [];

  for (let { label, values } of data) {
    elements.push(
      <div>
        {label}: {values.join(", ")}
      </div>
    );
  }

  return <div>{elements}</div>;
};

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.

Anything we missed?

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.