UNPKG

react-exo-hooks

Version:

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

169 lines (168 loc) 4.8 kB
import { useState, useEffect } from 'react'; /** * This is an array that causes rerenders on updates * @note Effects and memos that use this array should also listen for its signal: `+INSTANCE` */ export class StatefulArray extends Array { /** The dispatch function for the signal */ _dispatchSignal; /** The update signal */ _signal; /** THe dispatch function for redefining the array */ _dispatchRedefine; /** * Construct a StatefulArray * @param initial The initial value (parameter for a vanilla array) * @param dispatchSignal The dispatch function for the signal */ constructor(initial, dispatchSignal) { if (initial) super(...initial); else super(); 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 there's no redefinition callback defined */ reset(value) { if (!this._dispatchRedefine) throw new Error('Cannot redefine Array. No redefine callback set.'); const instance = new StatefulArray(value, this._dispatchSignal); if (this._signal === 0) instance._signal = 1; // Force update this._dispatchRedefine(instance); instance._dispatchSignal?.(instance._signal); return instance; } /** * @override */ copyWithin(target, start, end) { let different = false; for (let offset = 0; offset < (end ?? this.length - 1); ++offset) { if (this[target + offset] !== this[start + offset]) { different = true; break; } } if (different) this._dispatchSignal?.(++this._signal); return super.copyWithin(target, start, end); } /** * @override */ fill(value, start, end) { let different = false; for (let i = start ?? 0; i < (end ?? this.length - 1); ++i) { if (this[i] !== value) { different = true; break; } } if (different) this._dispatchSignal?.(++this._signal); return super.fill(value, start, end); } /** * @override */ pop() { if (this.length) this._dispatchSignal?.(++this._signal); return super.pop(); } /** * @override */ push(...items) { if (items.length) this._dispatchSignal?.(++this._signal); return super.push(...items); } /** * @override */ reverse() { let palindrome = true; for (let i = 0; i < Math.floor(this.length / 2); ++i) { if (this[i] !== this[this.length - i - 1]) { palindrome = false; break; } } if (!palindrome) this._dispatchSignal?.(++this._signal); return super.reverse(); } /** * @override */ shift() { if (this.length) this._dispatchSignal?.(++this._signal); return super.shift(); } /** * @override */ sort(compareFn) { // No way to efficiently compare this without copying; always signal this._dispatchSignal?.(++this._signal); return this.sort(compareFn); } /** * @override */ splice(start, deleteCount, ...rest) { if (deleteCount || rest.length) this._dispatchSignal?.(++this._signal); return super.splice(start, deleteCount, ...rest); } /** * @override */ unshift(...items) { if (items.length) this._dispatchSignal?.(++this._signal); return super.unshift(...items); } /** * Returns the array's signal. Used for effects and memos that use this array * @returns A numeric signal */ valueOf() { return this._signal; } } /** * Use a stately array * @note Any effects or memos that use this set should also listen for its signal (`+INSTANCE`) * @param initial The initial array value * @returns The stately array */ export function useArray(initial) { const [, setSignal] = useState(0); const [array, setArray] = useState(new StatefulArray(initial, setSignal)); useEffect(() => array._setRedefine(setArray), [array]); return array; }