I'm using tRPC in a project with already existing API calls and would like to wrap the useQuery
function into a separate wrapper, but I'm struggling with getting the TypeScript types right.
Here is what the ideal outcome looks like. Instead of the following code:
const query = trpc.myendpoint.useQuery({ foo: 'bar' });
... I would like to write this:
const query = useCustomHook(trpc.myendpoint, { foo: 'bar' });
The JavaScript part is easy to implement but I struggle in getting the TypeScript types right. I want the useCustomHook
to return the correct data type. Here is my any
hack which technically works but provides no type hints:
function useCustomHook(obj: any, queryData: any) {
// ... Here I have some code related to my old code, which is why I need the custom hook
return obj.useQuery(queryData);
}
I tried the code below already and also tried inferring types but did not get it to work. These were some of my approaches in which I already did not know how to type queryData
properly:
// Approach using the trpc object
type TrpcProcedure1 = (typeof trpc)[keyof typeof trpc];
function useCustomHook<T extends TrpcProcedure>(obj: TrpcProcedure1, queryData: ???) {}
// Error when calling obj.useQuery:
// Property 'useQuery' does not exist on type 'TrpcProcedure'.
// Approach going via AppRouter['_def']['procedures']
type TrpcProcedure2 = (typeof trpc)[keyof AppRouter['_def']['procedures']];
function useCustomHook<T extends TrpcProcedure>(obj: TrpcProcedure2, queryData: ???) {}
// Error when calling obj.useQuery:
// This expression is not callable. Each member of the union type 'ProcedureUseQuery<...>' has signatures, but none of those signatures are compatible with each other
Unfortunately, both resulted in error messages as written.
I managed to get it to work (thanks to help from the tRPC discord):
import { DecorateProcedure, UseTRPCQueryResult } from "@trpc/react-query/shared";
import { AnyQueryProcedure, inferProcedureInput, inferProcedureOutput } from "@trpc/server";
import { TRPCClientErrorLike } from "@trpc/client";
function useCustomHook<QueryProcedure extends AnyQueryProcedure, U, V extends string>(
proc: DecorateProcedure<QueryProcedure, U, V>,
params: inferProcedureInput<QueryProcedure>
): UseTRPCQueryResult<inferProcedureOutput<QueryProcedure>, TRPCClientErrorLike<QueryProcedure>> {
// custom code...
return proc.useQuery(params);
}
Using this code, the input as well as the output is properly typed.
DecorateProcedure<QueryProcedure, U, V>
is the procedure type, where QueryProcedure extends AnyQueryProcedure
. The input parameters can then be inferred via inferProcedureInput<QueryProcedure>
. Similarly, the output type can be inferred via inferProcedureOutput<QueryProcedure>
and then wrapped into UseTRPCQueryResult
.