loading...
Cover image for Using Javascript Sets with React useState

Using Javascript Sets with React useState

ganes1410 profile image Ganesh R Updated on ・2 min read

Recently, I wanted to try something different. To use a Set object as a react state value.

Initially, I tried to find examples of this online but could not find anything useful or relatable. So, let me explain Sets with an example of my own.

First, we have a list of products and we are filtering the products based on the brand(s) selected by the user.

My initial idea for this is to use an array to store the filters (selected brand names) I need to apply and append it to query params in the url. But then I thought Set would be a more apt data structure to go for.

Let's say we have three brands - Nike, Adidas and Puma; and a list of products each belonging to one of the above three brands.

The initial state that holds the user selected brands is as below.

const [selectedBrands,setSelectedBrands] = useState(new Set());

So, whenever we click on a brand, we need to add the brand to the set object and if that brand is already present in the Set, we need to remove it. And based on the current state of the selected brands, we need to render the products list.

const brands = ["Nike", "Adidas", "Puma"];

function App() {

  function handleBrandSelection(brand) {
    if (selectedBrands.has(brand)) {
      selectedBrands.delete(brand);
      setSelectedBrands(selectedBrands);
    } else {
      selectedBrands.add(brand);
      setSelectedBrands(selectedBrands);
    }
  }

  return (
    <>
      {/* Brands Section */}
      <section className="filters">
        {brands.map(brand => (
          <div onClick={() => handleBrandSelection(brand)}>
            // brandName
            {brand}
            // Condition to show the checkmark whether it is selected
            {selectedBrands.has(brand) ? (
              <span
                role="img"
                aria-label="checked icon"
                className="checked-icon"
              >
                
              </span>
            ) : null}
          </div>
        ))}
      </section>

      {/* Products Section */}
      <section className="products">
        {/* List of products rendered here */}
      </section>
    </>
  );
}



Clearly, the above code would NOT work.

This is because while updating, we are sending the same Set object that we used in State. Since it has the same reference in memory, React will not update it.

The solution for this is to create a new Set object whenever we change the Set object. So, in the above example, let's create a new Set object while deleting and adding values to the State.


function handleBrandSelection(brand) {

  /*
   * This creates a new Set object based on
   * previous Set object passed as an argument
   * In this case, it is the selected Brands
   */

  const newSet = new Set(selectedBrands);
  if (selectedBrands.has(brand)) {
    newSet.delete(brand);
    setSelectedBrands(newSet);
  } else {
    newSet.add(brand);
    setSelectedBrands(newSet);
  }
}

There is another way to do this if we do not create set object every time while updating the value. That is we can wrap our set in an array. Here is a example of that by David.K Piano

Here is a modest codesandbox example

I hope this is useful to you! Feel free to leave your thoughts in the comments section.

Posted on by:

Discussion

pic
Editor guide
 

Thanks for showing that it's possible to use Set instead of Array to handle React state. But what's the benefit of using this data structure? Checking item existence in a collection with has instead of includes seems like not a big deal.
Btw, I see a disadvantage of using Set instead of Array. If you need to render a list of selectedBrands, you'd need to convert it to array before iteration.

 

Thanks for your reply. I agree that Array would be enough for most use cases. But if you have to maintain a list in the state that requires constant insertion and removal of items Set would be a good use-case for that in my opinion.

Coming to your second question. I would not use Set to store values that need to rendered. Instead of that, it should be used to store the state which modifies the values we are rendering like the example I used in the article.

 

But if you have to maintain a list in the state that requires constant insertion and removal of items Set would be a good use-case for that in my opinion.

I really don't understand why Set is better in this case. Is it performance, because has is faster than includes?

I would not use Set to store values that need to rendered.

Well, it happened to me many times that project requirements change, and tomorrow you'll need to render values that today are stored in Set. Extra work to refactor Set to Array.

Hi, I am not saying Set should be the only thing used in this case. I am just saying it can be done using this way too. You are free to use whatever method suitable for your requirements.

With Set you don't have to implement duplication control manually, it provides it to you for free. If you add the same value for the second time, Set will just ignore it, while with Array you'll have the duplicate and you'll have to add more code to handle this case.

 

Thanks for this post and the David K. tweet ... now as I really want to use a Set for filtering long log entries, I'm wondering if the correct way isn't to use a Set in the useRef box, with eventually a useEffect kind of reset ?

What do you think ?

 

Hi, I used Set values to render some UI. So I used it as state. I think we can also use it as an ref too. But I have not tried that.

 

I fell into this very trap yesterday, spent a while trying to work out why modifying my set didn’t cause the state to update.

It’s a pity that set isn’t immutable by default, perhaps for performance reasons?

I do prefer the has(), add(), delete() syntax but it’d be even nicer if they just returned a new, immutable set in place, rather than having to pass the results to a new Set(currentSet) again.

I might revisit just using an array with a filter
setState(currentState.includes(val) ? currentState.filter(x => x !== val) : [val, ...currentState])
Although apparently IE doesn’t support Array.includes()...