@usefy/use-set
v0.25.1
Published
A React hook for managing Set state with immutable updates
Maintainers
Readme
Overview
@usefy/use-set manages a JavaScript Set as React state with immutable, ergonomic updates. Every mutation produces a brand-new Set (so React re-renders correctly and the previous state is never mutated), and the returned set is typed as ReadonlySet to steer you toward the provided actions.
Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.
Why use-set?
- Zero Dependencies — Pure React implementation
- TypeScript First — Full
<T>generics with exported types - Immutable Updates — New
Seton every change;ReadonlySetreturn type prevents accidental in-place mutation - Complete Action Set —
add,remove,toggle,has,clear,reset - Smart
toggle— Optionalforceargument to set membership explicitly (likeDOMTokenList.toggle) - Stable Actions — Action identities never change, so they're safe as
useEffectdependencies - No Wasted Renders — No-op updates (adding an existing value, removing an absent value, clearing an empty set) are skipped
- Lazy Initialization — Accepts a
Set, an iterable, or a factory — just likeuseState
Installation
# npm
npm install @usefy/use-set
# yarn
yarn add @usefy/use-set
# pnpm
pnpm add @usefy/use-setPeer Dependencies
This package requires React 18 or 19:
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}Quick Start
import { useSet } from "@usefy/use-set";
function ItemList({ items }: { items: Item[] }) {
const [selected, { toggle, has }] = useSet<string>();
return (
<ul>
{items.map((item) => (
<li key={item.id}>
<label>
<input
type="checkbox"
checked={has(item.id)}
onChange={() => toggle(item.id)}
/>
{item.name}
</label>
</li>
))}
</ul>
);
}API Reference
useSet<T>(initialState?)
Returns a tuple of the current read-only set and a stable actions object.
Parameters
| Parameter | Type | Default | Description |
| -------------- | -------------------- | ------- | -------------------------------------------------------- |
| initialState | SetInitializer<T> | empty | A Set, an iterable of values, or a factory returning one (evaluated once) |
Returns [set, actions]
| Item | Type | Description |
| --------- | ------------------- | ------------------------------------------------- |
| set | ReadonlySet<T> | Current set. Read via has/size/iteration |
| actions | UseSetActions<T> | Stable action handlers (see below) |
Actions
| Action | Signature | Description |
| -------- | -------------------------------------- | ------------------------------------------------------------------------------ |
| add | (value: T) => void | Add a value. Adding an existing value is a no-op |
| remove | (value: T) => void | Remove a value. Removing an absent value is a no-op |
| toggle | (value: T, force?: boolean) => void | Flip membership, or set it explicitly with force (true = add, false = remove) |
| has | (value: T) => boolean | Whether the set contains a value (stable, always reflects latest state) |
| clear | () => void | Remove all values. Clearing an empty set is a no-op |
| reset | () => void | Restore the initial values (a fresh copy) |
The returned
setis aReadonlySet, so callingset.add(...)directly is a TypeScript error. Use the actions — mutating the set in place would bypass React state and break re-renders.
Examples
Multi-select with toggle
import { useSet } from "@usefy/use-set";
function SelectableList({ items }: { items: Item[] }) {
const [selectedIds, { toggle, has, clear }] = useSet<string>(["1", "2"]);
return (
<div>
{items.map((item) => (
<Checkbox
key={item.id}
checked={has(item.id)}
onChange={() => toggle(item.id)}
/>
))}
<button onClick={clear}>Clear ({selectedIds.size})</button>
</div>
);
}toggle with force (controlled membership)
const [enabled, { toggle }] = useSet<string>();
// Ensure present / absent regardless of current state
toggle("darkMode", true); // add
toggle("darkMode", false); // removeTag filter with reset
const [activeTags, { add, remove, reset }] = useSet<string>(["react", "hooks"]);
add("typescript");
remove("hooks");
reset(); // back to the initial tagsStable actions as effect dependencies
const [ids, actions] = useSet<string>();
useEffect(() => {
const unsub = subscribe((id) => actions.add(id));
return unsub;
}, [actions]); // actions never changes identity — effect runs onceTypeScript
import {
useSet,
type SetInitializer,
type UseSetActions,
type UseSetReturn,
} from "@usefy/use-set";
const [set, actions]: UseSetReturn<number> = useSet<number>([1, 2, 3]);Behavior Notes
- Immutable — Actions never mutate the current set; they replace it with a new one. Any snapshot you captured stays valid.
- Referentially stable actions — The actions object and each function keep the same identity for the lifetime of the component.
- Initial value is copied — The set you pass in is never mutated, and
resetalways yields a fresh copy of it. - No-op skipping — Updates that wouldn't change anything don't create a new set or trigger a re-render.
Testing
This package maintains comprehensive test coverage to ensure reliability and stability.
Test Coverage
📊 View Detailed Coverage Report (GitHub Pages)
Test Files
useSet.test.ts— 21 tests for hook behavior and immutability
Total: 21 tests
License
MIT © mirunamu
This package is part of the usefy monorepo.
