react-query-factory
Version:
Create API client for your React app with just one line of code, using React Query!
135 lines (130 loc) • 9.01 kB
TypeScript
import { UseQueryOptions, QueryKey, UseQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
type ServiceFunction<TParams = undefined, TResult = unknown> = [
TParams
] extends [undefined] ? (params?: TParams) => Promise<TResult> : (params: TParams) => Promise<TResult>;
type UseQueryFnWithoutParams<TResult = unknown> = (options?: Omit<UseQueryOptions<TResult, unknown, TResult, QueryKey>, 'queryKey' | 'queryFn'>) => UseQueryResult<TResult, unknown>;
type UseQueryFnWithParams<TParams, TResult = unknown> = (params: TParams, options?: Omit<UseQueryOptions<TResult, unknown, TResult, QueryKey>, 'queryKey' | 'queryFn'>) => UseQueryResult<TResult, unknown>;
/**
* Creates a custom `useQuery` hook tailored to a specific service function. This utility
* function facilitates the integration of asynchronous service functions with React Query's
* `useQuery` hook, allowing for data fetching with or without parameters.
*
* @template TResult The expected result type of the service function's promise.
* @template TParams The type of parameters the service function expects, if any.
*
* @param {object} params Configuration object for creating the custom `useQuery` hook.
* @param {boolean} params.expectsParams Indicates whether the service function expects parameters.
* If `true`, the generated `useQuery` hook will expect the first argument to be the parameters
* for the service function. If `false`, the hook will not expect any parameters for the service function.
* @param {ServiceFunction<TParams, TResult>} params.serviceFn The service function that will be
* invoked by the `useQuery` hook. This function should return a promise that resolves to the data
* you want to fetch.
* @param {(params: TParams) => QueryKey} [params.queryKey] An optional function to generate a custom
* query key based on the parameters passed to the service function. If provided, it overrides the default
* query key generation. This is useful for ensuring that queries are correctly invalidated and refetched
* by React Query when parameters change.
*
* @returns {UseQueryFnWithParams<TParams, TResult> | UseQueryFnWithoutParams<TResult>} A custom `useQuery`
* hook that is either parameterized (if `expectsParams` is `true`) or non-parameterized (if `expectsParams`
* is `false`). The hook, when invoked within a React component, will call the provided `serviceFn` with
* the specified parameters (if any) and manage the fetching state (loading, error, data) using React Query.
*
* @example
* // Service function without parameters
* const fetchTodos = () => fetch('/api/todos').then(res => res.json());
* const useTodosQuery = createUseQuery({ expectsParams: false, serviceFn: fetchTodos });
* // Inside a component
* const { data: todos, isLoading } = useTodosQuery();
*
* @example
* // Service function with parameters
* const fetchTodoById = (todoId) => fetch(`/api/todos/${todoId}`).then(res => res.json());
* const useTodoQuery = createUseQuery({ expectsParams: true, serviceFn: fetchTodoById, queryKey: (todoId) => ['todo', todoId] });
* // Inside a component
* const { data: todo, isLoading } = useTodoQuery(todoId);
*/
declare function createUseQuery<TResult>(params: {
expectsParams: false;
serviceFn: ServiceFunction<undefined, TResult>;
queryKey?: (params?: undefined) => QueryKey;
}): UseQueryFnWithoutParams<TResult>;
declare function createUseQuery<TParams, TResult>(params: {
expectsParams: true;
serviceFn: ServiceFunction<TParams, TResult>;
queryKey?: (params: TParams) => QueryKey;
}): UseQueryFnWithParams<TParams, TResult>;
type FunctionType = (...args: any[]) => any;
type CreateUseQueryTypeWithoutParams<T extends Record<keyof T, FunctionType>> = {
[K in keyof T]: Parameters<T[K]>[0] extends undefined ? {
useQuery: UseQueryFnWithoutParams<ReturnType<T[K]> extends Promise<infer R> ? R : never>;
} : unknown;
};
type CreateUseQueryTypeWithParams<T extends Record<keyof T, FunctionType>> = {
[K in keyof T]: Parameters<T[K]>[0] extends undefined ? unknown : {
useQuery: UseQueryFnWithParams<Parameters<T[K]>[0], ReturnType<T[K]> extends Promise<infer R> ? R : never>;
};
};
type CreateUseQueryType<T extends Record<keyof T, FunctionType>> = CreateUseQueryTypeWithoutParams<T> & CreateUseQueryTypeWithParams<T>;
type UseMutationFnWithoutParams<TResult> = (options?: Omit<UseMutationOptions<TResult, unknown, void, unknown>, 'mutationFn'>) => UseMutationResult<TResult, unknown, void, unknown>;
type UseMutationFnWithParams<TParams, TResult> = (options?: Omit<UseMutationOptions<TResult, unknown, TParams, unknown>, 'mutationFn'>) => UseMutationResult<TResult, unknown, TParams, unknown>;
declare function createUseMutation<TResult>(serviceFn: ServiceFunction<undefined, TResult>): UseMutationFnWithoutParams<TResult>;
declare function createUseMutation<TParams, TResult>(serviceFn: ServiceFunction<TParams, TResult>): UseMutationFnWithParams<TParams, TResult>;
type FunctionConstraint<T> = {
[K in keyof T]: (...args: any[]) => any;
};
/**
* Dynamically generates query and mutation hooks, along with query key generators,
* for each function in a given service object. This utility function facilitates the
* integration of service layer functions with React Query's useQuery and useMutation hooks.
*
* @template T The type of the service object, constrained to an object where each property
* is a function that might take any number of arguments and return a Promise of any type.
* @param {T} service An object containing service functions. Each function in this object
* represents a distinct API call or data fetching logic that can be transformed into
* a query or a mutation.
* @param {string} queryKeyPrefix A string prefix used to construct the query keys for
* each generated query. This prefix helps in namespacing the queries and avoiding key collisions.
*
* @returns {Queries} An object where each key corresponds to a key in the input service object,
* and each value is an object containing:
* - `useQuery`: A custom hook generated by `createUseQuery`, tailored to invoke the corresponding
* service function. If the service function does not expect parameters, `useQuery` will be a
* function that takes optional React Query options. If the service function expects parameters,
* `useQuery` will be a function that expects those parameters followed by optional React Query options.
* - `useMutation`: A custom hook generated by `createUseMutation`, tailored to invoke the corresponding
* service function. Similar to `useQuery`, the shape of `useMutation` depends on whether the service
* function expects parameters.
* - `queryKey`: A function that generates a query key for the corresponding service function. The generated
* query key is an array consisting of the `queryKeyPrefix`, the key corresponding to the service function,
* and the parameters passed to the service function, if any.
*
* The `Queries` type is a mapped type where each property of the input service object is mapped to an object
* containing the `useQuery`, `useMutation`, and `queryKey` properties as described above.
*
* @example
* ```tsx
* const userService = {
* getUser: (params: {id: string}) => fetch(`/api/users/${params.id}`).then(res => res.json()),
* updateUser: (userId, userData) => fetch(`/api/users/${userId}`, {
* method: 'POST',
* body: JSON.stringify(userData),
* }).then(res => res.json()),
* };
*
* const { getUser, updateUser } = createQueriesFromService(userService, 'user');
*
* // Inside a component
* const Foo = () => {
* const { data: user, isLoading } = getUser.useQuery({ id: "123" });
* const { mutate: updateUser } = updateUser.useMutation();
*
* return <div>...</div>
* }
* ```
*/
declare function createQueriesFromService<T extends FunctionConstraint<T>>(service: T, queryKeyPrefix: string): { [K in keyof T]: {
useQuery: T[K] extends infer T_1 ? T_1 extends T[K] ? T_1 extends ServiceFunction<infer TParams, infer TResult> ? Parameters<T[K]> extends infer T_2 ? T_2 extends Parameters<T[K]> ? T_2 extends [] ? UseQueryFnWithoutParams<TResult> : UseQueryFnWithParams<TParams, TResult> : never : never : never : never : never;
useMutation: T[K] extends infer T_3 ? T_3 extends T[K] ? T_3 extends ServiceFunction<infer TParams_1, infer TResult_1> ? Parameters<T[K]> extends infer T_4 ? T_4 extends Parameters<T[K]> ? T_4 extends [] ? UseMutationFnWithoutParams<TResult_1> : UseMutationFnWithParams<TParams_1, TResult_1> : never : never : never : never : never;
queryKey: (params: Parameters<T[K]> extends [infer Params] ? Params : void) => QueryKey;
}; };
export { CreateUseQueryType, CreateUseQueryTypeWithParams, CreateUseQueryTypeWithoutParams, ServiceFunction, UseMutationFnWithParams, UseMutationFnWithoutParams, UseQueryFnWithParams, UseQueryFnWithoutParams, createQueriesFromService, createUseMutation, createUseQuery };