UNPKG

@ts-rest/react-query

Version:

react-query client integration for @ts-rest

180 lines (177 loc) 8.64 kB
import { useQueryClient, useQuery, useQueries, useInfiniteQuery, useMutation } from '@tanstack/react-query'; import { isAppRoute, getRouteQuery, evaluateFetchApiArgs, fetchApi, isErrorResponse } from '@ts-rest/core'; import { useMemo } from 'react'; const queryFn = (route, clientArgs, args) => { return async (queryFnContext) => { const fetchApiArgs = evaluateFetchApiArgs(route, clientArgs, args); const result = await fetchApi({ ...fetchApiArgs, fetchOptions: { ...((queryFnContext === null || queryFnContext === void 0 ? void 0 : queryFnContext.signal) && { signal: queryFnContext.signal }), ...fetchApiArgs.fetchOptions, }, }); // If the response is not a 2XX, throw an error to be handled by react-query if (isErrorResponse(result)) { throw result; } return result; }; }; const getRouteUseQuery = (route, clientArgs) => { return (queryKey, args, options) => { const dataFn = queryFn(route, clientArgs, args); return useQuery({ queryKey, queryFn: dataFn, ...options }); }; }; const getRouteUseQueries = (route, clientArgs) => { return (args) => { const queries = args.queries.map((fullQueryArgs) => { const { credentials, queryKey, retry, ...queryArgs } = fullQueryArgs; const dataFn = queryFn(route, clientArgs, queryArgs); return { queryFn: dataFn, ...fullQueryArgs, }; }); return useQueries({ queries, context: args.context }); }; }; const getRouteUseInfiniteQuery = (route, clientArgs) => { return (queryKey, argsMapper, options) => { const dataFn = async (context) => { const resultingQueryArgs = argsMapper(context); const innerDataFn = queryFn(route, clientArgs, resultingQueryArgs); return innerDataFn(undefined); }; return useInfiniteQuery({ queryKey, queryFn: dataFn, ...options }); }; }; const getRouteUseMutation = (route, clientArgs) => { return (options) => { const mutationFunction = async (args) => { const dataFn = queryFn(route, clientArgs, args); return dataFn(undefined); }; return useMutation({ mutationFn: mutationFunction, ...options, }); }; }; const ClientParameters = Symbol('ClientParameters'); const initQueryClient = (router, clientArgs) => { const recursiveInit = (innerRouter) => { return Object.fromEntries(Object.entries(innerRouter).map(([key, subRouter]) => { if (isAppRoute(subRouter)) { return [ key, { query: getRouteQuery(subRouter, clientArgs), mutation: getRouteQuery(subRouter, clientArgs), useQuery: getRouteUseQuery(subRouter, clientArgs), useQueries: getRouteUseQueries(subRouter, clientArgs), useInfiniteQuery: getRouteUseInfiniteQuery(subRouter, clientArgs), useMutation: getRouteUseMutation(subRouter, clientArgs), fetchQuery: (queryClient, queryKey, args, options) => { const dataFn = queryFn(subRouter, clientArgs, args); return queryClient.fetchQuery({ queryKey, queryFn: dataFn, ...options, }); }, fetchInfiniteQuery: (queryClient, queryKey, argsMapper, options) => { return queryClient.fetchInfiniteQuery({ queryKey, queryFn: async (context) => { const resultingQueryArgs = argsMapper(context); const innerDataFn = queryFn(subRouter, clientArgs, resultingQueryArgs); return innerDataFn(undefined); }, ...options, }); }, prefetchQuery: (queryClient, queryKey, args, options) => { const dataFn = queryFn(subRouter, clientArgs, args); return queryClient.prefetchQuery({ queryKey, queryFn: dataFn, ...options, }); }, prefetchInfiniteQuery: (queryClient, queryKey, argsMapper, options) => { return queryClient.prefetchInfiniteQuery({ queryKey, queryFn: async (context) => { const resultingQueryArgs = argsMapper(context); const innerDataFn = queryFn(subRouter, clientArgs, resultingQueryArgs); return innerDataFn(undefined); }, ...options, }); }, getQueryData: (queryClient, queryKey, filters) => { return queryClient.getQueryData(queryKey, filters); }, ensureQueryData: (queryClient, queryKey, args, options) => { const dataFn = queryFn(subRouter, clientArgs, args); return queryClient.ensureQueryData({ queryKey, queryFn: dataFn, ...options, }); }, getQueriesData: (queryClient, filters) => { return queryClient.getQueriesData(filters); }, setQueryData: (queryClient, queryKey, updater) => { return queryClient.setQueryData(queryKey, updater); }, }, ]; } else { return [key, recursiveInit(subRouter)]; } })); }; return { ...recursiveInit(router), [ClientParameters]: { router, clientArgs, }, }; }; const useTsRestQueryClient = (client) => { // @ts-expect-error - hidden symbol, so we can refetch the original client router and clientArgs const { router } = client[ClientParameters]; const queryClient = useQueryClient(); const recursiveInit = (innerRouter, innerClient) => { return Object.fromEntries(Object.entries(innerRouter).map(([key, subRouter]) => { if (isAppRoute(subRouter)) { const routeFunctions = innerClient[key]; return [ key, { ...routeFunctions, fetchQuery: (queryKey, args, options) => routeFunctions.fetchQuery(queryClient, queryKey, args, options), fetchInfiniteQuery: (queryKey, argsMapper, options) => routeFunctions.fetchInfiniteQuery(queryClient, queryKey, argsMapper, options), prefetchQuery: (queryKey, args, options) => routeFunctions.prefetchQuery(queryClient, queryKey, args, options), prefetchInfiniteQuery: (queryKey, argsMapper, options) => routeFunctions.prefetchInfiniteQuery(queryClient, queryKey, argsMapper, options), getQueryData: (queryKey, filters) => routeFunctions.getQueryData(queryClient, queryKey, filters), ensureQueryData: (queryKey, args, options) => routeFunctions.ensureQueryData(queryClient, queryKey, args, options), getQueriesData: (filters) => routeFunctions.getQueriesData(queryClient, filters), setQueryData: (queryKey, updater) => routeFunctions.setQueryData(queryClient, queryKey, updater), }, ]; } else { return [key, recursiveInit(subRouter, innerClient[key])]; } })); }; return useMemo(() => recursiveInit(router, client), [client]); }; export { initQueryClient, useTsRestQueryClient };