javascriptreactjstypescriptnext.jstrpc.io

Wrap useQuery of tRPC in a custom hook with correct TypeScript types


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.

What I want

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' });

Non-Typescript Solution

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);
}

What I tried so far

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.


Solution

  • 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.