import localForage from "localforage"; import isEqual from "lodash-es/isEqual"; import React, { Dispatch, SetStateAction, useEffect } from "react"; import { View } from "src/components/List/views"; import { ConfigImageLightboxInput } from "src/core/generated-graphql"; interface IInterfaceQueryConfig { filter: string; itemsPerPage: number; currentPage: number; } export interface IViewConfig { showSidebar?: boolean; } type IQueryConfig = Record; interface IInterfaceConfig { queryConfig: IQueryConfig; imageLightbox: ConfigImageLightboxInput; // Partial is required because using View makes the key mandatory viewConfig: Partial>; } export interface IChangelogConfig { versions: Record; } interface ILocalForage { data?: T; error: Error | null; loading: boolean; } const Loading: Record = {}; const Cache: Record = {}; export function useLocalForage( key: string, defaultValue: T = {} as T ): [ILocalForage, Dispatch>] { const [error, setError] = React.useState(null); const [data, setData] = React.useState(Cache[key] as T); const [loading, setLoading] = React.useState(Loading[key]); useEffect(() => { async function runAsync() { try { let parsed = await localForage.getItem(key); if (typeof parsed === "string") { parsed = JSON.parse(parsed ?? "null"); } if (parsed !== null) { setData(parsed); Cache[key] = parsed; } else { setData(defaultValue); Cache[key] = defaultValue; } setError(null); } catch (err) { if (err instanceof Error) setError(err); Cache[key] = defaultValue; } finally { Loading[key] = false; setLoading(false); } } if (!loading && !Cache[key]) { Loading[key] = true; setLoading(true); runAsync(); } }, [loading, key, defaultValue]); useEffect(() => { if (!isEqual(Cache[key], data)) { Cache[key] = { ...Cache[key], ...data, }; localForage.setItem(key, Cache[key]); } }); const isLoading = loading || loading === undefined; return [{ data, error, loading: isLoading }, setData]; } export const useInterfaceLocalForage = () => useLocalForage("interface"); export const useChangelogStorage = () => useLocalForage("changelog");