- refactor state structure to include app, layout, navigation, owner, program, session, and user - add slices for managing program, session, owner, user, layout, navigation, and app states - create context provider for global state with automatic fetching and throttling - implement persistence using IndexedDB with localStorage fallback - add comprehensive README documentation for usage and API
108 lines
2.6 KiB
TypeScript
108 lines
2.6 KiB
TypeScript
import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
|
|
|
import type { GlobalStateStoreType } from './GlobalStateStore.types';
|
|
|
|
import { GetGlobalState, GlobalStateStore } from './GlobalStateStore';
|
|
|
|
|
|
interface GlobalStateStoreContextValue {
|
|
fetchData: (url?: string) => Promise<void>;
|
|
getState: () => GlobalStateStoreType;
|
|
refetch: () => Promise<void>;
|
|
}
|
|
|
|
const GlobalStateStoreContext = createContext<GlobalStateStoreContextValue | null>(null);
|
|
|
|
|
|
interface GlobalStateStoreProviderProps {
|
|
apiURL?: string;
|
|
autoFetch?: boolean;
|
|
children: ReactNode;
|
|
fetchOnMount?: boolean;
|
|
throttleMs?: number;
|
|
}
|
|
|
|
export function GlobalStateStoreProvider({
|
|
apiURL,
|
|
autoFetch = true,
|
|
children,
|
|
fetchOnMount = true,
|
|
throttleMs = 0,
|
|
}: GlobalStateStoreProviderProps) {
|
|
const lastFetchTime = useRef<number>(0);
|
|
const fetchInProgress = useRef<boolean>(false);
|
|
const mounted = useRef<boolean>(false);
|
|
|
|
|
|
const throttledFetch = useCallback(
|
|
async (url?: string) => {
|
|
const now = Date.now();
|
|
const timeSinceLastFetch = now - lastFetchTime.current;
|
|
|
|
if (fetchInProgress.current) {
|
|
return;
|
|
}
|
|
|
|
if (throttleMs > 0 && timeSinceLastFetch < throttleMs) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
fetchInProgress.current = true;
|
|
lastFetchTime.current = now;
|
|
await GlobalStateStore.getState().fetchData(url);
|
|
} finally {
|
|
fetchInProgress.current = false;
|
|
}
|
|
},
|
|
[throttleMs]
|
|
);
|
|
|
|
const refetch = useCallback(async () => {
|
|
await throttledFetch();
|
|
}, [throttledFetch]);
|
|
|
|
useEffect(() => {
|
|
if (apiURL) {
|
|
GlobalStateStore.getState().setApiURL(apiURL);
|
|
}
|
|
}, [apiURL]);
|
|
|
|
useEffect(() => {
|
|
if (!mounted.current) {
|
|
mounted.current = true;
|
|
|
|
if (autoFetch && fetchOnMount) {
|
|
throttledFetch(apiURL).catch((e) => {
|
|
console.error('Failed to fetch on mount:', e);
|
|
});
|
|
}
|
|
}
|
|
}, [apiURL, autoFetch, fetchOnMount, throttledFetch]);
|
|
|
|
const context = useMemo(() => {
|
|
return {
|
|
fetchData: throttledFetch,
|
|
getState: GetGlobalState,
|
|
refetch,
|
|
};
|
|
}, [throttledFetch, refetch]);
|
|
|
|
|
|
|
|
return (
|
|
<GlobalStateStoreContext.Provider value={context}>
|
|
{children}
|
|
</GlobalStateStoreContext.Provider>
|
|
);
|
|
}
|
|
|
|
// eslint-disable-next-line react-refresh/only-export-components
|
|
export function useGlobalStateStoreContext(): GlobalStateStoreContextValue {
|
|
const context = useContext(GlobalStateStoreContext);
|
|
if (!context) {
|
|
throw new Error('useGlobalStateStoreContext must be used within GlobalStateStoreProvider');
|
|
}
|
|
return context;
|
|
}
|