UNPKG

react-native-shared-state

Version:

Create shared states that can be connected to multiple react native components, allowing for simple global state management.

228 lines (183 loc) 5.9 kB
import ExtendedError from 'extended_err'; import { ComponentRegister } from './ComponentRegister'; import { EventRegister } from './EventRegister'; import { StateCache } from './StateCache'; import { StorageHandler } from './StorageHandler'; import { onMount, onUnMount, useReRender } from '../helpers'; import { State, StorageOptions, UpdateKeys } from '../types'; type StateOptions = { debugLabel?: string; }; export class SharedState<S extends State> { private debugLabel: string; private componentRegister: ComponentRegister<S>; private eventRegister: EventRegister<S>; private stateCache: StateCache<S>; private storageHandler: StorageHandler<S>; constructor(defaultState: S, options: StateOptions = {}) { const { debugLabel } = options; this.debugLabel = debugLabel; this.componentRegister = new ComponentRegister(); this.stateCache = new StateCache(defaultState); this.eventRegister = new EventRegister(this.stateCache); this.debugger(this); } get state() { return this.stateCache.current; } set state(object) { throw new ExtendedError({ name: 'State Error', code: 'UPDATE_STATE_ERROR', message: 'State cannot be mutated directly, use the setState() method instead.', severity: 'MEDIUM', }); } get prevState() { return this.stateCache.prev; } set prevState(object) { throw new ExtendedError({ name: 'State Error', code: 'UPDATE_PREV_STATE_ERROR', message: 'Prev state is read only.', severity: 'MEDIUM', }); } setState(partialState: Partial<S>, callback?: () => void) { try { const updatedState: Partial<S> = {}; let updated = false; for (const key in partialState) { // To reduce unwanted component re-renders only update props that have changed if (this.stateCache.updateProp(key, partialState[key])) { // changed prop added to updatedState updatedState[key] = partialState[key]; updated = true; } } // Only send if a change has occured if (updated) { this.componentRegister.update(updatedState); this.eventRegister.run(updatedState); this.debugger({ send: updatedState }); } if (callback) callback(); } catch (error) { throw ExtendedError.transform(error, { name: 'State Error', code: 'UPDATE_STATE_ERROR', message: 'Update state error', severity: 'HIGH', }); } } refresh(callback?: () => void) { try { this.componentRegister.update(true); if (callback) callback(); } catch (error) { throw ExtendedError.transform(error, { name: 'State Error', code: 'REFRESH_STATE_ERROR', message: 'Refresh state error', severity: 'HIGH', }); } } reset(resetData?: S, callback?: () => void) { try { this.stateCache.reset(resetData); this.componentRegister.update(this.stateCache.current); if (this.storageHandler) this.storageHandler.reset(resetData); this.debugger({ resetState: this.stateCache.current }); if (callback) callback(); } catch (error) { throw ExtendedError.transform(error, { name: 'State Error', code: 'RESET_STATE_ERROR', message: 'Reset state error', severity: 'HIGH', }); } } register(component: React.Component, updateKeys: UpdateKeys<S>) { const sharedStateId = Symbol('Shared State ID'); function reRenderComponent() { component.setState({ [sharedStateId]: Symbol('Shared State Updater') }); } this.componentRegister.register(component, updateKeys, reRenderComponent); this.debugger({ register: { component, updateKeys } }); } unregister(component: React.Component) { this.componentRegister.unregister(component); this.debugger({ unregister: { component } }); } // EVENTS addListener( trigger: keyof S | (keyof S)[], callback: (current: S, prev: Partial<S>) => void, ) { return this.eventRegister.add(trigger, callback); } // HOOKS useState( updateKey: UpdateKeys<S>, ): [S, (partialState: Partial<S>, callback?: () => void) => void] { const componentId = Symbol('Hook ID'); const reRenderComponent = useReRender(); onMount(() => { this.componentRegister.register( componentId, updateKey, reRenderComponent, ); this.debugger({ registerHook: { componentId, updateKey } }); }); onUnMount(() => { this.componentRegister.unregister(componentId); this.debugger({ unregisterHook: { componentId } }); }); const setValue = (partialState: Partial<S>, callback?: () => void) => { this.setState(partialState, callback); }; return [this.state, setValue]; } // STORAGE PERSIST async initializeStorage(options: StorageOptions) { this.storageHandler = this.storageHandler || new StorageHandler<S>(this.stateCache, options); try { this.reset(await this.storageHandler.get()); return true; } catch (error) { const storageError = ExtendedError.transform(error, { name: 'State Error', code: 'STORAGE_ERROR', message: 'Error loading from storage', severity: 'HIGH', }); this.reset(); storageError.handle(); return false; } } useStorage(options: StorageOptions, callback?: () => void) { onMount(async () => { await this.initializeStorage(options); if (callback) callback(); }); } save() { this.debugger(`Storing ${this.storageHandler.options.storeName}`); return this.storageHandler.save(); } // DEBUGGING debugger(...log: any[]) { if (this.debugLabel) console.log(this.debugLabel, ...log); } toString() { return JSON.stringify(this.state, null, 2); } }