useSet
EditManages an immutable Set with add, remove, and toggle controls.
import { useCallback, useEffect, useLayoutEffect, useRef, useState,} from "react";
export type SetValues<Value> = ReadonlySet<Value> | readonly Value[];
export interface UseSetResult<Value> { readonly set: ReadonlySet<Value>; readonly add: (value: Value) => void; readonly addMany: (values: Iterable<Value>) => void; readonly remove: (value: Value) => void; readonly toggle: (value: Value) => void; readonly clear: () => void; readonly reset: () => void;}
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
function copyValues<Value>(values: SetValues<Value>): Set<Value> { if (Array.isArray(values)) { for (let index = 0; index < values.length; index += 1) { if (!Object.prototype.hasOwnProperty.call(values, index)) { throw new TypeError("values must not be sparse"); } } }
return new Set(values);}
export function useSet<Value>( initialValues: SetValues<Value> = [],): UseSetResult<Value> { const resetSnapshot = copyValues(initialValues); const [set, setSet] = useState(() => resetSnapshot); const initialRef = useRef<ReadonlySet<Value>>(resetSnapshot);
useIsomorphicLayoutEffect(() => { initialRef.current = resetSnapshot; });
const add = useCallback((value: Value) => { setSet((current) => { if (current.has(value)) return current; const next = new Set(current); next.add(value); return next; }); }, []);
const addMany = useCallback((values: Iterable<Value>) => { const materialized = Array.isArray(values) ? Array.from(copyValues(values)) : Array.from(values); if (materialized.length === 0) return;
setSet((current) => { const next = new Set(current); for (const value of materialized) next.add(value); return next.size === current.size ? current : next; }); }, []);
const remove = useCallback((value: Value) => { setSet((current) => { if (!current.has(value)) return current; const next = new Set(current); next.delete(value); return next; }); }, []);
const toggle = useCallback((value: Value) => { setSet((current) => { const next = new Set(current); if (next.has(value)) next.delete(value); else next.add(value); return next; }); }, []);
const clear = useCallback(() => { setSet((current) => (current.size === 0 ? current : new Set())); }, []);
const reset = useCallback(() => { setSet(new Set(initialRef.current)); }, []);
return { set, add, addMany, remove, toggle, clear, reset };}Download
Section titled “Download”wget -O src/hooks/useSet.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/react-hooks/useSet.tstemp_dir="$(mktemp -d)" && bunx degit jrTilak/lazykit/registry/react-hooks "$temp_dir" && mv "$temp_dir/useSet.ts" src/hooks/useSet.ts && rm -r "$temp_dir"Examples
Section titled “Examples”import { useSet } from "./useSet";
const availableTags = ["react", "typescript", "testing"] as const;
export function SetExample() { const selected = useSet<(typeof availableTags)[number]>(["react"]);
return ( <fieldset> <legend>Tags</legend> {availableTags.map((tag) => ( <label key={tag}> <input type="checkbox" checked={selected.set.has(tag)} onChange={() => selected.toggle(tag)} /> {tag} </label> ))} </fieldset> );}initialValues(ReadonlySet<Value> | readonly Value[], default:[]) — Initial values and latest reset snapshot. Sparse arrays are rejected, while an explicitundefinedremains a valid value when included inValue.
Returns
Section titled “Returns”UseSetResult<Value> with readonly set, add, addMany, remove, toggle,
clear, and reset. Duplicate additions and missing removals retain the
existing Set identity.