UNPKG

state-management-utilities

Version:
179 lines (178 loc) 5.33 kB
import { produce } from "immer"; import cloneDeep from "lodash.clonedeep"; import { center } from "./center"; /** * This class provides methods to register, un-register, update, and retrieve state, ensuring components are updated efficiently and consistently. */ export class StateManager { _initialValue; _configs; _value; /** * The current state change promise. * * @protected * @type {Promise<any>} */ _fulfill; /** * Returns the promise to full fill the current state change. * * @returns the instance of the state manager for method chaining. */ async fulfill() { if (this._fulfill) await this._fulfill; return this; } /** * A dictionary of callbacks registered in the state manager. * The key is the unique identifier of the callback, and the value is the callback function. * The callback function receives the new state whenever it is updated. */ _callbacks = {}; constructor( /** * Initial state of the manager. */ _initialValue, /** * Options to configure the state manager. */ _configs = { uid: `SM-#${counter++}`, }) { this._initialValue = _initialValue; this._configs = _configs; this._value = this._clone(this._initialValue); // TODO: Should it log the initial state? // eslint-disable-next-line no-self-assign // this._value = this._value; this._configs.onChange?.(this._value); center._register({ uid: this._configs.uid, stateManager: this, }); } get value() { return this._clone(this._value); } set value(newState) { this._setValueHandler(newState); } hydrate(value) { return { update: (record) => { if (value !== undefined) record[this.uid] = { value, }; }, value, }; } /** * Handles setting a new state value. * * This method clones the new state (if cloning is not disabled) and assigns it to the internal `_value` property. * It then logs the new state using the `center._log` method. * * After logging, it asynchronously triggers the `onChange` callback if it exists, * and iterates over the `_callbacks` object to call each callback with the new state. * * @param newState - The new state to be set. */ _setValueHandler(newState) { this._value = this._clone(newState); center._log({ uid: this._configs.uid, state: newState, }); // this._fulfill = (async () => { // await this._configs.onChange?.(newState); // for (const setterId in this._callbacks) { // /* istanbul ignore next */ // await this._callbacks?.[setterId]?.(newState); // } // })().catch(console.error); try { this._configs.onChange?.(newState); for (const setterId in this._callbacks) { /* istanbul ignore next */ this._callbacks?.[setterId]?.(newState); } } catch (error) { console.error(error); } } /** * Gets the unique identifier (uid) from the configuration. * * @returns {string} The unique identifier. */ get uid() { return this._configs.uid; } /** * Registers a new callback in the state manager. */ register({ uid, callback, }) { if (this._callbacks[uid]) { throw new Error(`Callback with uid of ${uid} is already registered in ${this.uid}.`); } this._callbacks[uid] = callback; return this; } /** * Unregister a callback from the state manger. */ unregister({ uid, }) { delete this._callbacks[uid]; return this; } /** * Returns the unique IDs of the registered callbacks. */ get registeredCallbacks() { return Object.keys(this._callbacks); } /** * Triggers the state manager to update the components. */ trigger() { // eslint-disable-next-line no-self-assign this.value = this.value; } /** * Resets the state manager to its initial state. */ reset() { this.value = this._initialValue; } /** * Clones the given state value if cloning is not disabled. * * @param value - The state value to be cloned. * @returns The cloned state value if cloning is enabled; otherwise, returns the original value. */ _clone(value) { return this._configs.disableCloning || center.disableCloning ? value : cloneDeep(value); } get initialValue() { return this._clone(this._initialValue); } /** * Updates the current state using the provided updater function (it utilizes immer js). * * @param updater - A function that takes the previous state and returns the new state. * @returns The current instance for method chaining. */ update(updater) { this.value = produce(this._value, updater); return this; } } let counter = 0;