@usefy/use-map
v0.25.1
Published
A React hook for managing Map state with immutable updates
Maintainers
Readme
Overview
@usefy/use-map manages a JavaScript Map as React state with immutable, ergonomic updates. Every mutation produces a brand-new Map (so React re-renders correctly and the previous state is never mutated), and the returned map is typed as ReadonlyMap 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-map?
- Zero Dependencies — Pure React implementation
- TypeScript First — Full
<K, V>generics with exported types - Immutable Updates — New
Mapon every change;ReadonlyMapreturn type prevents accidental in-place mutation - Complete Action Set —
set,setAll,remove,reset,clear,get - Stable Actions — Action identities never change, so they're safe as
useEffectdependencies - No Wasted Renders — No-op updates (removing an absent key, clearing an empty map, setting a key to its current value) are skipped
- Lazy Initialization — Accepts a
Map, an iterable of tuples, or a factory — just likeuseState
Installation
# npm
npm install @usefy/use-map
# yarn
yarn add @usefy/use-map
# pnpm
pnpm add @usefy/use-mapPeer Dependencies
This package requires React 18 or 19:
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}Quick Start
import { useMap } from "@usefy/use-map";
function Settings() {
const [prefs, { set, remove, reset }] = useMap<string, boolean>([
["darkMode", false],
["beta", true],
]);
return (
<label>
<input
type="checkbox"
checked={prefs.get("darkMode") ?? false}
onChange={(e) => set("darkMode", e.target.checked)}
/>
Dark mode
</label>
);
}API Reference
useMap<K, V>(initialState?)
Returns a tuple of the current read-only map and a stable actions object.
Parameters
| Parameter | Type | Default | Description |
| -------------- | --------------------- | ------- | -------------------------------------------------------- |
| initialState | MapInitializer<K, V> | empty | A Map, an iterable of [key, value] tuples, or a factory returning one (evaluated once) |
Returns [map, actions]
| Item | Type | Description |
| --------- | --------------------- | --------------------------------------------------- |
| map | ReadonlyMap<K, V> | Current map. Read via get/has/size/iteration |
| actions | UseMapActions<K, V> | Stable action handlers (see below) |
Actions
| Action | Signature | Description |
| ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------- |
| set | (key: K, value: V) => void | Set/overwrite a key. Setting a key to its current value is a no-op |
| setAll | (entries: Iterable<[K, V]>) => void | Replace the entire map with the given entries |
| remove | (key: K) => void | Remove a key. Removing an absent key is a no-op |
| reset | () => void | Restore the initial entries (a fresh copy) |
| clear | () => void | Remove all entries. Clearing an empty map is a no-op |
| get | (key: K) => V \| undefined | Read a key's value (stable, always reflects latest state) |
The returned
mapis aReadonlyMap, so callingmap.set(...)directly is a TypeScript error. Use the actions — mutating the map in place would bypass React state and break re-renders.
Examples
User directory (objects keyed by id)
import { useMap } from "@usefy/use-map";
interface User {
id: string;
name: string;
}
function UserDirectory() {
const [users, { set, remove }] = useMap<string, User>([
["1", { id: "1", name: "Alice" }],
["2", { id: "2", name: "Bob" }],
]);
const addUser = (user: User) => set(user.id, user);
const removeUser = (id: string) => remove(id);
return (
<ul>
{[...users.values()].map((user) => (
<li key={user.id}>
{user.name}
<button onClick={() => removeUser(user.id)}>Remove</button>
</li>
))}
</ul>
);
}Replace everything with setAll
const [cache, { setAll, clear }] = useMap<string, Product>();
async function refresh() {
const products = await fetchProducts();
setAll(products.map((p) => [p.id, p])); // replaces the whole cache
}Reset to initial
const [form, { set, reset }] = useMap<string, string>([
["email", ""],
["name", ""],
]);
set("email", "[email protected]");
reset(); // both fields back to ""Stable actions as effect dependencies
const [items, actions] = useMap<string, Item>();
useEffect(() => {
const unsub = subscribe((item) => actions.set(item.id, item));
return unsub;
}, [actions]); // actions never changes identity — effect runs onceTypeScript
import {
useMap,
type MapInitializer,
type UseMapActions,
type UseMapReturn,
} from "@usefy/use-map";
const [map, actions]: UseMapReturn<number, string> = useMap<number, string>([
[1, "one"],
]);Behavior Notes
- Immutable — Actions never mutate the current map; 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 map 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 map 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
useMap.test.ts— 22 tests for hook behavior and immutability
Total: 22 tests
License
MIT © mirunamu
This package is part of the usefy monorepo.
