import { gqlFetcherFactory, type GraphQLQuery, type QueryOptions, type QueryResponse, } from '@affine/graphql'; import type { GraphQLError } from 'graphql'; import { useCallback, useMemo } from 'react'; import type { SWRConfiguration, SWRResponse } from 'swr'; import useSWR from 'swr'; import useSWRImmutable from 'swr/immutable'; import useSWRInfinite from 'swr/infinite'; /** * A `useSWR` wrapper for sending graphql queries * * @example * * ```ts * import { someQuery, someQueryWithNoVars } from '@affine/graphql' * * const swrResponse1 = useQuery({ * query: workspaceByIdQuery, * variables: { id: '1' } * }) * * const swrResponse2 = useQuery({ * query: someQueryWithNoVars * }) * ``` */ type useQueryFn = ( options?: QueryOptions, config?: Omit< SWRConfiguration< QueryResponse, GraphQLError, (options: QueryOptions) => Promise> >, 'fetcher' > ) => SWRResponse< QueryResponse, GraphQLError, { suspense: true; } >; const createUseQuery = (immutable: boolean): useQueryFn => (options, config) => { const configWithSuspense: SWRConfiguration = useMemo( () => ({ suspense: true, ...config, }), [config] ); const useSWRFn = immutable ? useSWRImmutable : useSWR; return useSWRFn( options ? () => ['cloud', options.query.id, options.variables] : null, options ? () => gqlFetcher(options) : null, configWithSuspense ); }; export const useQuery = createUseQuery(false); export const useQueryImmutable = createUseQuery(true); export const gqlFetcher = gqlFetcherFactory('/graphql', window.fetch); export function useQueryInfinite( options: Omit, 'variables'> & { getVariables: ( pageIndex: number, previousPageData: QueryResponse ) => QueryOptions['variables']; }, config?: Omit< SWRConfiguration< QueryResponse, GraphQLError | GraphQLError[], (options: QueryOptions) => Promise> >, 'fetcher' > ) { const configWithSuspense: SWRConfiguration = useMemo( () => ({ suspense: true, ...config, }), [config] ); const { data, setSize, size, error } = useSWRInfinite< QueryResponse, GraphQLError | GraphQLError[] >( (pageIndex: number, previousPageData: QueryResponse) => [ 'cloud', options.query.id, options.getVariables(pageIndex, previousPageData), ], async ([_, __, variables]) => { const params = { ...options, variables } as QueryOptions; return gqlFetcher(params); }, configWithSuspense ); const loadingMore = size > 0 && data && !data[size - 1]; // TODO(@Peng): find a generic way to know whether or not there are more items to load const loadMore = useCallback(() => { if (loadingMore) { return; } setSize(size => size + 1).catch(err => { console.error(err); }); }, [loadingMore, setSize]); return { data, error, loadingMore, loadMore, }; }