Build a custom React hook that manages key/value pairs in state. A Map is the natural structure for "look up a value by its key" — a cart keyed by product id, form errors keyed by field name, a selection keyed by row id. But Map is mutable: map.set(k, v) and map.delete(k) change the existing object in place. React decides whether to re-render by checking whether you handed it a new value, not by inspecting the contents — so a mutated Map looks unchanged and the screen goes stale. useMap(initialEntries) owns the Map and hands back the current map plus four helpers that change it the right way: set, remove, get, and clear.
function useMap<K, V>(initialEntries?: Array<[K, V]> | Map<K, V>): {
map: Map<K, V>;
set: (key: K, value: V) => void;
remove: (key: K) => void;
get: (key: K) => V | undefined;
clear: () => void;
};
initialEntries defaults to an empty list when omitted, and accepts either an array of [key, value] pairs or an existing Map. set adds or overwrites an entry; remove deletes the entry for a key; get reads the value for a key (or undefined if absent); clear empties the Map.
function PriceList() {
const { map, set, remove, clear } = useMap([['apple', 1]]);
return (
<div>
{[...map].map(([name, price]) => (
<button key={name} onClick={() => remove(name)}>{name}: {price} x</button>
))}
<button onClick={() => set('banana', 2)}>add banana</button>
<button onClick={clear}>clear</button>
</div>
);
}
// shows apple: 1; "add banana" -> apple: 1, banana: 2; clicking "apple" -> banana: 2
// Each mutating helper returns a brand-new Map; the old one is never changed.
const { map, set, remove, get } = useMap([['a', 1]]);
set('b', 2); // Map { a => 1, b => 2 }
set('a', 99); // Map { a => 99, b => 2 } (overwrites)
get('a'); // 99
get('missing'); // undefined
remove('a'); // Map { b => 2 }
map.set(k, v) changes the existing Map 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 freezes.new Map(prev), then apply the change to the copy. new Map(prev) is a shallow copy, which is exactly what reference-equality needs.get is a plain read. It returns from the current Map and does not trigger a re-render; missing keys return undefined, not an error.remove, not delete. delete is a reserved word in JavaScript, awkward to expose as a method, so the hook returns it as remove.