import * as React from "react"; import { useEffect, useState } from "react"; import { setItem, getItem } from "./storage"; export type StoreConfig = { initialValues: T; baseKey?: string; debug?: boolean; }; //local variables let debug = false; let contexts: { [key: string]: React.Context } = {}; type Keys = Extract; const getKey = (key: string, baseKey: string) => `${baseKey}.${key}`; const contextKey = (key: string) => `${key}Context`; export type UseStoreType = >( key: K ) => [TStore[K], (value: TStore[K]) => Promise, { hydrated: boolean }]; const StoreContextProvider = >({ DynamicContext, children, config, storeKey, }: { DynamicContext: React.Context; children: any; config: StoreConfig; storeKey: K; }) => { const [hydrated, setHydrated] = useState(false); const [store, setStore] = useState( config?.initialValues[storeKey] ); //null or some object or string or whatever const baseKey = config?.baseKey || "useStore"; // On mount, get the current value from storage useEffect(() => { const fullKey = getKey(storeKey, baseKey); getItem(fullKey).then((value) => { if (debug) { console.log(`Hydrated store for ${fullKey}:`, value); } setStore(value); setHydrated(true); }); }, []); const initialValues = config?.initialValues; const useStoreHook: UseStoreType = >( key: K2 ) => { const fullKey = getKey(key, baseKey); const defaultValue = initialValues[key]; // @ts-ignore const value: TStore[K2] = store !== undefined ? store : defaultValue !== undefined ? defaultValue : null; const dispatch: (value: TStore[K2]) => Promise = async (value) => { //should do a deep equal here, and only set the store and item if the value actually has changed //@ts-ignore setStore(value); await setItem(fullKey, value); }; return [value, dispatch, { hydrated }]; }; return ( {children} ); }; export const createStoreProvider = ( config: StoreConfig ) => { if (config.debug) { debug = config.debug; } if (debug) { console.log("Create StoreProvider"); } const keys = Object.keys(config.initialValues) as Keys[]; contexts = keys.reduce((acc, key) => { const Context = React.createContext(null); return { ...acc, [contextKey(key)]: Context, }; }, {}); const MainProvider = ({ children }: { children: any }) => keys.reduce((acc, key) => { const context = contexts[contextKey(key)]; return ( {acc} ); }, children); return ({ children }: { children: any }) => { return {children}; }; }; const getContext = (key: string) => contexts[contextKey(key)]; export const createUseStore = ( initialValues: TState ) => { if (debug) { console.log("Create useStore"); } const useStore = >(key: K) => { if (!Object.keys(initialValues).includes(key)) { throw new Error(`Using undefined key in useStore: ${key}`); } const context = getContext(key); if (!context) { throw new Error( `Failed loading the context with key: ${key}. Did you wrap your component/app with a StoreProvider?` ); } const useStoreHook = React.useContext>(context); const useStoreHookType = typeof useStoreHook; if (useStoreHookType !== "function") { console.error( `useStoreHookType for ${key} is ${useStoreHookType}... wtf?` ); } return useStoreHook(key); }; return useStore; };