Using Sets for Efficient Filtering in JavaScript

Filtering a collection of data can be a tricky task, especially when the data is repetitive. In this case, we're filtering a collection from Contentful, which is a collection of quizzes filtered by topic and grade level. Given the repetitive nature of the quizzes, I decided to use a Set() instead of an array.

A Set is a collection of unique values. It has different methods from a primitive array, but I believe they are worth learning. For one thing, a Set doesn't have a .length property like a primitive array. This means that we will have to use the .size property to check the size of the Set.

Example

// Renders a filtered collection or the entire collection depending 
// on the size of the filtered Set()
return filteredQuizCards.size > 0 ? (
  <Container>
    // Turn it into an array from the Set() so that it can be mapped
    {Array.from(filteredQuizCards).map((data: any) => {
      return <QuizCard key={data.fields.quizTitle} data={data.fields.quizCard} />;
    })}
  </Container>
) : (
  <Container>
    {quizCollection.map(data => {
      return <QuizCard key={data.fields.quizTitle} data={data.fields.quizCard} />;
    })}
  </Container>
);

Filtering by the quiz various selections creates an array within an array. To get past the array of arrays dilemma, I found the .flat() method. The .flat() method creates a new array with all sub-array elements concatenated into it recursively up to the specified depth. This is useful when we want to flatten an array of arrays.

// Topic filter
const output = currentTopics
  .map(topic => quizCollection.filter(quiz => quiz.fields.filters.fields.topic.includes(topic)))
  .flat(Infinity);

A Set has a method called add(). We can use this method to add the output to the Set easily.

// Only difficulty has been selected
const output = currentDifficultySelection
  .map(difficulty => quizCollection.filter(quiz => quiz.fields.filters.fields.difficulty.includes(difficulty)))
  .flat(Infinity);

output.map(card => filteredQuizCards.add(card));

This works great! Now, let's say we want to combine the filters. For example, we want to filter by both topics and difficulty selection. We can do this by filtering both outputs by each other.

// Filter by topics
const topicOutput = currentTopics
  .map(topic => quizCollection.filter(quiz => 
    quiz.fields.filters.fields.topic.includes(topic)))
  .flat(Infinity);

// Filter by difficulty selection
const difficultyOutput = currentDifficultySelection
  .map(difficulty => quizCollection.filter(quiz => 
    quiz.fields.filters.fields.difficulty.includes(difficulty)))
  .flat(Infinity);

// Filter both outputs by each other
const doubleFiltered = difficultyOutput
  .map((difficultyCard: any) =>
    topicOutput

In conclusion, using a Set to filter a collection of data can be a powerful tool in certain cases. In this example, we were able to filter a collection of quizzes from Contentful by topic and grade level using a Set. The .flat() method was also used to flatten an array of arrays.

The Set() doesn't have a .length property like a primitive array, but instead has .size property to check the number of element inside set and .add() method to add new element. Furthermore, we were also able to combine multiple filters to create a more specific set of data.

Using a Set can be a little more difficult to understand for other developers, but it can make your code more efficient and effective. If you decide to use a Set, make sure to document your code well and be prepared to explain your thought process to your team.