UNPKG

react-exo-hooks

Version:

A collection of useful hooks for data structures and logic, designed for efficiency

114 lines (113 loc) 3.45 kB
import { useState, useEffect } from 'react'; /** * This is a set that causes rerenders on updates * @note Effects and memos that use this set should also listen for its signal: `+INSTANCE` */ export class StatefulMap extends Map { /** The dispatch function for the signal */ _dispatchSignal; /** The update signal */ _signal; /** THe dispatch function for redefining the set */ _dispatchRedefine; /** * Construct a StatefulSet * @param initial The initial value (parameter for a vanilla set) * @param dispatchSignal The dispatch function for the signal */ constructor(initial, dispatchSignal) { super(initial); this._signal = 0; this._dispatchSignal = dispatchSignal; } /** * Set the redefine dispatch * @private * @param callback The function */ _setRedefine(callback) { this._dispatchRedefine = callback; } /** * Force a signal update */ forceUpdate() { this._dispatchSignal?.(++this._signal); } /** * Set the instance to an entirely new instance * @param value The new instance * @returns The new instance * @throws {Error} If no redefinition callback is defined */ reset(value) { if (!this._dispatchRedefine) throw new Error('Cannot redefine Set. No redefine callback set.'); const instance = new StatefulMap(value, this._dispatchSignal); instance._signal = this._signal; this._dispatchRedefine(instance); instance._dispatchSignal?.(++instance._signal); return instance; } /** * @override */ set(key, value) { const old = super.get(key); const newKey = !this.has(key); super.set(key, value); if (newKey || old !== value) this._dispatchSignal?.(++this._signal); return this; } /** * Bulk set an array of items * @note Always rerenders * @param items An array of items * @param keyFn Either the name of a property of each item or a function that returns the key for each item * @returns this */ bulkSet(items, keyFn) { for (const item of items) { const key = typeof keyFn === 'function' ? keyFn(item) : item[keyFn]; super.set(key, item); } this._dispatchSignal?.(++this._signal); return this; } /** * @override */ delete(key) { const returnValue = super.delete(key); if (returnValue) this._dispatchSignal?.(++this._signal); return returnValue; } /** * @override */ clear() { super.clear(); this._dispatchSignal?.(this._signal = 0); } /** * Returns the set's signal. Used for effects and memos that use this set * @returns The numeric signal */ valueOf() { return this._signal; } } /** * Use a stately set * @note Any effects or memos that use this set should also listen for its signal (`+INSTANCE`) * @param initial The initial set value * @returns The stately set */ export function useMap(initial) { const [, setSignal] = useState(Array.isArray(initial) ? initial.length : initial?.size ?? 0); const [map, setMap] = useState(new StatefulMap(initial, setSignal)); useEffect(() => map._setRedefine(setMap), [map]); return map; }