react-exo-hooks
Version:
A collection of useful hooks for data structures and logic, designed for efficiency
114 lines (113 loc) • 3.45 kB
JavaScript
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;
}