import * as React from "react"; import { useGetKeyById } from "./useGetKey"; import { usePagination } from "./usePagination"; import { useGetSet } from "./useStore"; export interface Result { data: R; error: Error | null; loading: boolean; mutate: () => void; } export function useDatabase( id: string, initialPageSize: number = 25, intialPage: number = 1, queryFn: (limit: number, offset: number) => Q, fetcherFn: (p: Q, signal?: AbortSignal) => Promise, autoload?: boolean, ): Result { type State = { data: R | null; count: number | null; error: Error | null; loading: boolean; }; const key = useGetKeyById(id); const [{ data, count, error, loading }, setState] = useGetSet(key, { data: null, count: null, error: null, loading: autoload ?? true, }); const { pageSize, after, currentPage, setCurrentPage, setPageSize, totalPages, hasPrevious, hasNext, nextPage, previousPage, } = usePagination(count || 1, initialPageSize ?? 25, intialPage ?? 1); const signalRef = React.useRef(null); const queryRef = React.useRef(""); const run = React.useCallback((query: Q) => { setState({ loading: true }); fetcherFn(query, signalRef.current?.signal) .then( (data) => { if (data && (data as any)?.$$count !== undefined) { setState({ data, count: (data as any).$$count }); } else if (data) { setState({ data }); } if (data && (data as any)?.$$tags !== undefined) { document.dispatchEvent( new CustomEvent("REGISTER_TAGS", { detail: (data as any).$$tags, }), ); } }, (err) => { if (err?.name !== "AbortError") { setState({ error: err }); } }, ) .finally(() => setState({ loading: false })); }, []); const maybeMutate = React.useCallback(() => { const query = queryFn(pageSize, after); const queryKey = JSON.stringify(query); if (queryRef.current !== queryKey) { signalRef.current?.abort(); signalRef.current = new AbortController(); queryRef.current = queryKey; run(query); } }, [queryFn, pageSize, after, run]); const mutate = React.useCallback(() => { queryRef.current = ""; maybeMutate(); }, [maybeMutate]); React.useEffect( function revalidateOnReconnect() { if (autoload === false) return; const controller = new AbortController(); window.addEventListener("online", mutate, { signal: controller.signal, }); return () => controller.abort(); }, [mutate, autoload], ); React.useEffect( function revalidateOnVisibilityChange() { if (autoload === false) return; const controller = new AbortController(); window.addEventListener("visibilitychange", mutate, { signal: controller.signal, }); return () => controller.abort(); }, [mutate, autoload], ); React.useEffect(() => { if (autoload === false) return; maybeMutate(); }, undefined); const pagination = React.useMemo( () => ({ pageSize, after, currentPage, setCurrentPage, setPageSize, totalPages, hasPrevious, hasNext, nextPage, previousPage, }), [ pageSize, after, currentPage, setCurrentPage, setPageSize, totalPages, hasPrevious, hasNext, nextPage, previousPage, ], ); return React.useMemo( () => ({ data, count, error, loading, mutate, pagination, }), [data, count, error, loading, mutate, pagination], ) as any; } export function useDatabaseItem( id: string, rowId: string, queryFn: (rowId: string) => Q, fetcherFn: (p: Q, signal?: AbortSignal) => Promise, autoload?: boolean, ): Result { type State = { data: R | null; error: Error | null; loading: boolean; }; const key = useGetKeyById(id); const [{ data, error, loading }, setState] = useGetSet(key, { data: null, error: null, loading: autoload ?? true, }); const signalRef = React.useRef(null); const queryRef = React.useRef(""); const run = React.useCallback((query: Q) => { setState({ loading: true }); fetcherFn(query, signalRef.current?.signal) .then( (data) => { if (data) { setState({ data }); } if (data && (data as any)?.$$tags !== undefined) { document.dispatchEvent( new CustomEvent("REGISTER_TAGS", { detail: (data as any).$$tags, }), ); } }, (err) => { if (err?.name !== "AbortError") { setState({ error: err }); } }, ) .finally(() => setState({ loading: false })); }, []); const maybeMutate = React.useCallback(() => { const query = queryFn(rowId); const queryKey = JSON.stringify(query); if (queryRef.current !== queryKey) { signalRef.current?.abort(); signalRef.current = new AbortController(); queryRef.current = queryKey; run(query); } }, [queryFn, rowId, run]); const mutate = React.useCallback(() => { queryRef.current = ""; maybeMutate(); }, [maybeMutate]); React.useEffect( function revalidateOnReconnect() { if (autoload === false) return; const controller = new AbortController(); window.addEventListener("online", mutate, { signal: controller.signal, }); return () => controller.abort(); }, [mutate, autoload], ); React.useEffect( function revalidateOnVisibilityChange() { if (autoload === false) return; const controller = new AbortController(); window.addEventListener("visibilitychange", mutate, { signal: controller.signal, }); return () => controller.abort(); }, [mutate, autoload], ); React.useEffect(() => { if (autoload === false) return; maybeMutate(); }, undefined); return React.useMemo( () => ({ data, error, loading, mutate, }), [data, error, loading, mutate], ) as any; }