Build a custom React hook that manages a unique collection of values. A Set is the natural fit when you need membership without duplicates — selected row ids, expanded panel keys, a set of active filters. But a Set is mutable: set.add(x) and set.delete(x) change the existing object in place, and React decides whether to re-render by checking whether you handed it a new value, not by inspecting the contents. useSet(initialValues) owns the set and hands back the current set plus four helpers that change it the right way: add, remove, has, and clear.
function useSet<T>(initialValues?: Iterable<T>): {
set: Set<T>;
add: (item: T) => void;
remove: (item: T) => void;
has: (item: T) => boolean;
clear: () => void;
};
initialValues is any iterable (usually an array) and defaults to empty. add inserts a member; remove drops one; has returns whether a value is currently a member; clear empties the set back to an empty Set.
function TagPicker() {
const { set, add, remove, has } = useSet(['react']);
const toggle = (tag) => (has(tag) ? remove(tag) : add(tag));
return ['react', 'vue', 'svelte'].map((tag) => (
<button key={tag} aria-pressed={has(tag)} onClick={() => toggle(tag)}>
{tag}
</button>
));
}
// 'react' starts pressed; clicking 'vue' adds it; clicking 'react' again removes it
// Each mutating helper returns a brand-new Set; the old one is never changed.
const { set, add, remove, has } = useSet([1, 2]);
add(3); // set is now {1, 2, 3}
add(3); // still {1, 2, 3} — Sets ignore duplicates
remove(1); // {2, 3}
has(2); // true
set.add(item) changes the existing Set and keeps the same reference. React compares the new value to the old by reference, sees no change, and skips the re-render — so the screen goes stale.new Set(prev), then add or delete on the copy, and hand React that fresh object.Set is for. Removing a value that is not present is also a no-op, not an error.