linkedstate
Version:
Context alternative for react. Link multiple states within components using a unique key, and set them/update them globally.
102 lines (81 loc) • 3.9 kB
JavaScript
import { useEffect, useState, useCallback } from "react";
import isEqual from "./isEqual";
if (window) var global = window;
//Set the global/window object to store state. "But muh glowballs!" you say? Look at that uuid. Aint nothing gonna collide with that.
const lsUUID = "___LINKEDSTATE___ef40968a-d9ba-4eef-a1b2-e020c5845fd1";
if (!global[lsUUID]) global[lsUUID] = {};
const awaitSetter = async (key, value) => {
//If setting a thenable, wait for it to finish before firing.
let awaitedResource = await value;
let resource = await awaitedResource;
_LS_set(key, resource);
};
const updateAllWatchers = (key, value) => {
//Update all components watching the state
for (const localStateSetter of global[lsUUID][key].localStateSetters) {
localStateSetter(value);
}
};
const _LS_set = (key, value) => {
//Set a linked state value by its key, and then update all watching components with this value
if (value && typeof value.then === "function") {
awaitSetter(key, value);
return;
}
//Initialize the linked state entry if it doesnt yet exist
if (!global[lsUUID][key]) global[lsUUID][key] = { currentValue: value, localStateSetters: [] };
//Deep check the new value for equlity before setting it to avoid unnecessary dom thrashing
if (isEqual(global[lsUUID][key].currentValue, value)) return;
global[lsUUID][key].currentValue = value;
//Update all watching commponents with the new value.
updateAllWatchers(key, value);
};
const _LS_watch = (key, localStateSetter, initialValue = undefined) => {
if (key === undefined) return;
if (!global[lsUUID][key]) {
//Initialize and exit if this is the only watching component.
global[lsUUID][key] = { currentValue: initialValue, localStateSetters: [localStateSetter] };
return;
}
//Add the local state setter if it doesnt exist yet in the registry.
if (!global[lsUUID][key].localStateSetters.find((otherStateSetter) => otherStateSetter === localStateSetter)) {
//Apply current linkedstate value to the new watcher.
localStateSetter((lastLocalValue) => (isEqual(lastLocalValue, global[lsUUID][key].currentValue) ? lastLocalValue : global[lsUUID][key].currentValue));
//...then local setter to registry
global[lsUUID][key].localStateSetters.push(localStateSetter);
}
};
const _LS_unwatch = (key, localStateSetter) => {
//Component cleanup when dismounted
if (key === undefined) return;
if (!global[lsUUID][key].localStateSetters) return;
if (global[lsUUID][key].localStateSetters.find((otherStateSetter) => otherStateSetter === localStateSetter)) {
global[lsUUID][key].localStateSetters = global[lsUUID][key].localStateSetters.filter((otherStateSetter) => otherStateSetter !== localStateSetter);
}
};
const _LS_get = (key) => {
//Universal get command of current linked state by key, does not require react to perform.
if (key === undefined || !global[lsUUID][key]) return undefined;
return global[lsUUID][key].currentValue;
};
//Getter and Setter for the global state
export const linkedState = {
get: _LS_get,
set: _LS_set,
};
//Hook for managing the local state
export const useLinkedState = (linkedStateKey, initialValue) => {
const [localState, setLocalState] = useState(global[lsUUID][linkedStateKey]?.currentValue || initialValue);
const globalStateUpdater = (newState) => {
if (typeof newState === "function") {
const lastState = linkedState.get(linkedStateKey);
newState = newState(lastState);
}
linkedState.set(linkedStateKey, newState);
};
useEffect(() => {
_LS_watch(linkedStateKey, setLocalState, initialValue);
return () => _LS_unwatch(linkedStateKey, setLocalState);
}, []);
return [localState, globalStateUpdater];
};