useSelection
State & dataMulti/single selection state for lists and tables — Set-based, checkbox-ready
Install
$ npm install @usefy/use-selectionQuick start
useSelection.tsx
import { useSelection } from "@usefy/use-selection";
type User = { id: number; name: string };
function UserTable({ users }: { users: User[] }) {
const {
selected,
isSelected,
toggle,
selectAll,
clear,
isAllSelected,
isPartiallySelected,
} = useSelection(users, { getKey: (u) => u.id });
return (
<table>
<thead>
<tr>
<th>
<input
type="checkbox"
checked={isAllSelected}
ref={(el) => {
if (el) el.indeterminate = isPartiallySelected;
}}
onChange={() => (isAllSelected ? clear() : selectAll())}
/>
</th>
<th>Name ({selected.length} selected)</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td>
<input
type="checkbox"
checked={isSelected(user)}
onChange={() => toggle(user)}
/>
</td>
<td>{user.name}</td>
</tr>
))}
</tbody>
</table>
);
}API reference
const result = useSelection<T>(items: T[], options?: UseSelectionOptions<T>);
Options — UseSelectionOptions<T>
| Option | Type | Default | Description |
|---|---|---|---|
getKey | (item: T) => string | number | identity ((item) => item) | Derives the selection key for an item. The Set stores these keys, not items, so selection survives new object references. Provide this for object items (e.g. (row) => row.id); the identity default is only correct for primitive items. |
multiple | boolean | true | true = multi-selection; false = single-selection (selecting replaces the current choice, and selectAll is a no-op). |
Return — UseSelectionReturn<T>
| Property | Type | Description |
|---|---|---|
selected | T[] | The selected items, in items order. Derived as items ∩ selectedKeys — items removed from items drop out automatically. |
selectedKeys | ReadonlySet<string | number> | The raw set of selected keys (source of truth). May contain keys for items no longer in items; those are invisible in the derived values. |
isSelected | (item: T) => boolean | Whether an item is selected (by key). Stable. |
toggle | (item: T) => void | Flip an item's selection. In single mode, selecting a new item replaces the previous one. Stable. |
select | (item: T) => void | Select an item (idempotent). In single mode, replaces the selection. Stable. |
deselect | (item: T) => void | Deselect an item (idempotent). Stable. |
selectAll | () => void | Select every item in items. No-op when all are already selected, or in single mode. Stable. |
clear | () => void | Clear the whole selection (deselect all). No-op when already empty. Stable. |
isAllSelected | boolean | true when every item is selected. Always false for an empty items array. |
isPartiallySelected | boolean | true when some — but not all — items are selected (drives an indeterminate header checkbox). false for empty items. |
isNoneSelected | boolean | true when no items are selected (including when items is empty). |
Behavior notes
- Selection key. The
Setstores keys, never items. This is what makes a selection stable whenitemsis rebuilt with fresh object references each render — as long asgetKeymaps the same logical item to the same key. For primitiveitems(string/number) the identity default just works. - Items-change reconciliation.
selectedKeysis the source of truth; every item-facing value is derived from the currentitems. Removing a selected row fromitemsmakes it disappear fromselectedand recomputesisAllSelected/isPartiallySelectedautomatically — no manual pruning needed. The removed key stays inselectedKeys(harmless and invisible) so it re-appears if the item comes back. - Empty list.
isAllSelectedisfalsefor an emptyitemsarray (there is nothing to have "all" selected);isNoneSelectedistrue. - Single-selection mode. With
multiple: false,select/toggleof a new item replace the current selection; toggling the already-selected item deselects it.selectAllis a no-op (you cannot hold more than one selection). - StrictMode / concurrency. Updates are immutable (a fresh
Seton every change) and no user callback runs inside asetStateupdater, so it is safe under StrictMode and concurrent rendering.
Go deeper
This page is the quick reference. For every example, prop, and edge case, read the full README — or open Storybook to change props live.