state-management-utilities
Version:
State management utilities
179 lines (178 loc) • 5.33 kB
JavaScript
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;