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.
Top comments (8)
Thanks for showing that it's possible to use
Set
instead ofArray
to handle React state. But what's the benefit of using this data structure? Checking item existence in a collection withhas
instead ofincludes
seems like not a big deal.Btw, I see a disadvantage of using
Set
instead ofArray
. If you need to render a list ofselectedBrands
, 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 itemsSet
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.I really don't understand why
Set
is better in this case. Is it performance, becausehas
is faster thanincludes
?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 refactorSet
toArray
.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 withArray
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 anew 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()...