React State how to add elements (not single element) immutably to a javascript Set (not Array)

Viewed 73

So I have the a react state as follows

[ state, setState] = useState(new Set([1,2,3]))

I want to add a bunch of elements to the set immutably, how can I do that? It is simple with Arrays and can be just done using setState(prev=>[...prev,...newElem]) but how do I do it in a set.

const newElems = new Set([4,5,6])  //These are new Elements that need to be appended
setState(prev=>new Set([..prev,...newElems]))   //This can be done and works but how do without converting Array to Set

The above implementation can be used, but how can it be done without the overhead of reassigning an Array to a Set every time. Can I just not append it somehow (immutably)

P.S This question is different from the one in the link below as it is about adding multiple elements not a single one. Also it uses Hooks approach in React vs Class based approach, sure they are interchangeable, but this question is more up to date and might help others Using a Set data structure in React's state

3 Answers

It is sadly the case that Set objects have no standard way of adding multiple elements after constructing the set. They were originally intentionally created minimalist with the idea of expanding them as needed. While there are some proposals for some features on the table, an addAll method or similar isn't on that list. (Yet. Although there's nothing in the proposals repo yet, I think there's been talk of adding immutable-style methods to Set and Map, or possibly immutable peer classes instead.)

You can create a Set subclass with and addAll method:

class MySet extends Set {
    addAll(iterable) {
        for (const element of iterable) {
            this.add(element);
        }
        return this;
    }
}

Then the update would be:

setState(prev => new MySet(prev).addAll(newElem));

Live Example:

class MySet extends Set {
    addAll(iterable) {
        for (const element of iterable) {
            this.add(element);
        }
        return this;
    }
}

const prev = new MySet();
prev.addAll([1, 2, 3]);
const newElem = [4, 5, 6]; // Or a `Set` or `MySet`

const set = new MySet(prev).addAll(newElem);
console.log([...set]);

Or you could add an optional argument to the constructor, but I'm always leery of adding arguments to subclass constructors:

class MySet extends Set {
    constructor(...iterables) {
        super();
        for (const iterable of iterables) {
            this.addAll(iterable);
        }
    }

    addAll(iterable) {
        for (const element of iterable) {
            this.add(element);
        }
        return this;
    }
}

Then the update would be:

setState(prev => new MySet(prev, newElem));

Live Example:

class MySet extends Set {
    constructor(...iterables) {
        super();
        for (const iterable of iterables) {
            this.addAll(iterable);
        }
    }

    addAll(iterable) {
        for (const element of iterable) {
            this.add(element);
        }
        return this;
    }
}

const prev = new MySet();
prev.addAll([1, 2, 3]);
const newElem = [4, 5, 6]; // Or a `Set` or `MySet`

const set = new MySet(prev, newElem);
console.log([...set]);

The overhead is really minimal and shouldn't impact performance at all given the array size is not in thousands. Also, I am assuming you are using set as you are having a lot of checks whether certain element is present in set or not. So it is more like a tradeoff that you are having by using the set.

You can also have a look at React Set state update answer

Have you tried just using the Set.add() function? you can map or loop over newElem's elements, add them to a new set, and then set the new set in state again.

Related