thomasplayschess
thomasplayschess13mo ago

Wrapping useQuery into a custom hook

I'm trying to wrap useQuery into a custom hook (as I have some legacy code that I need to run before). Example:
trpc.myendpoint.useQuery({ foo: 'bar' });
// instead of the code above, I want to write this:
useCustomHook(trpc.myendpoint, { foo: 'bar' })
trpc.myendpoint.useQuery({ foo: 'bar' });
// instead of the code above, I want to write this:
useCustomHook(trpc.myendpoint, { foo: 'bar' })
I'm able to write the JavaScript code, but I have trouble getting the TypeScript types right. I have more information in the Stackoverflow question here: https://stackoverflow.com/q/77096846 Thanks for any help ❤️
36 Replies
funki
funki13mo ago
@thomasplayschess TrpcProcedure is definitely not right because it could be a query or a mutation or an observable/subscription. if you wanna "hard-code" useQuery into your function without testing for the other possible types, use a narrower type instead. idk which type that would be but probably something like TrpcQueryProcedure. (might be named differently, just guessing). you'll find the right one for sure.
thomasplayschess
thomasplayschess13mo ago
I found DecorateProcedure which works like this: DecorateProcedure<AppRouter['_def']['record']['PROCNAME'], any, 'PROCNAME'> but was not able to make this work for all procedures. Using a narrower type works, but I want this to be generic. I get it to work for a single procedure, but I thought there must be a type that makes this work across all procedures.
funki
funki13mo ago
well first of all you don't want to make it work for all procedures obviously, only for the query procedures. otherwise you wouldn't ask for useQuery specifically. right?
thomasplayschess
thomasplayschess13mo ago
Oh, okay. My bad. You are right. Yes, sorry, wasn't sure on the wording.
funki
funki13mo ago
that type doesn't sound like what you're looking for. can you send the type definition of TrpcProcedure? it might be a union of the stuff you need.
thomasplayschess
thomasplayschess13mo ago
TrpcProcedure is the one that I made up in the stackoverflow question.
funki
funki13mo ago
oh
thomasplayschess
thomasplayschess13mo ago
For example this one: type TrpcProcedure = (typeof trpc)[keyof AppRouter['_def']['procedures']]; (which doesn't work)
funki
funki13mo ago
you wanna dive into tRPC's types. ctrl-click an existing, working useQuery it might be scary tho
thomasplayschess
thomasplayschess13mo ago
That's what I did to get to DecorateProcedure<AppRouter... but these types are really like A extends B extends C extends D | E extends F Seems it would take me days to get into it tbh 🙈
funki
funki13mo ago
i don't think you need to derive the generic type from your own router. it's possible to write a function like what you want that should work with any router.
thomasplayschess
thomasplayschess13mo ago
As you said, it's super scary...
funki
funki13mo ago
have you tried AnyQueryProcedure? i just looked around the tRPC docs and searched for "types" and eventually came across that.
thomasplayschess
thomasplayschess13mo ago
I think I used this somewhere. But honestly I think I've used up to 10 different interfaces up to now. Problem is that many are also generic and look just like the DecorateProcedure above (with 3 generics).
funki
funki13mo ago
you'll certainly need another type that the queryprocedure type takes as an argument in order to contract the params to it. i'm looking for a tRPC react playground right now
thomasplayschess
thomasplayschess13mo ago
Yeah, I had big problems regarding figuring out which interface/generic has which job... I'll give that AnyQueryProcedure another try that you've mentioned.
funki
funki13mo ago
no, that one ain't it because it's literally typed with any, but it's definition is a lead
thomasplayschess
thomasplayschess13mo ago
Yes 😦 This is the type I'm getting when looking at trpc.oneofmyroutes:
object & {
getQueryKey: (input: {
// MY INPUT PARAMS
}, type?: QueryType | undefined) => QueryKey;
useQuery: ProcedureUseQuery<...>;
useSuspenseQuery: <TQueryFnData extends {
...;
} = {
...;
}, TData = TQueryFnData>(input: {
// ALSO MY INPUT PARAMS (?)
}, opts?: Omit<...> | undefined) => [...];
}
object & {
getQueryKey: (input: {
// MY INPUT PARAMS
}, type?: QueryType | undefined) => QueryKey;
useQuery: ProcedureUseQuery<...>;
useSuspenseQuery: <TQueryFnData extends {
...;
} = {
...;
}, TData = TQueryFnData>(input: {
// ALSO MY INPUT PARAMS (?)
}, opts?: Omit<...> | undefined) => [...];
}
Thanks for taking the time btw! 🙂 Much appretiated
funki
funki13mo ago
okay seems like my instinct was wrong and you're already on the right track with DecorateProcedure
thomasplayschess
thomasplayschess13mo ago
At least I got that right... But no clue how I make DecorateProcedure<TProcedureOrRouter, TFlags, TPath> generic now 🙈
funki
funki13mo ago
@thomasplayschess i think i got it
const runProcedure = <
QueryProcedure extends AnyQueryProcedure,
U,
V extends string
>
(
proc: DecorateProcedure<QueryProcedure, U, V>,
params: inferProcedureInput<QueryProcedure>
) => {
return proc.useQuery(params)
}

runProcedure(trpc.foo, "aoidwoij")
const runProcedure = <
QueryProcedure extends AnyQueryProcedure,
U,
V extends string
>
(
proc: DecorateProcedure<QueryProcedure, U, V>,
params: inferProcedureInput<QueryProcedure>
) => {
return proc.useQuery(params)
}

runProcedure(trpc.foo, "aoidwoij")
about U and V: dont know + dont care
thomasplayschess
thomasplayschess13mo ago
AWESOME, I'll try it 🥳 YESSS
funki
funki13mo ago
@thomasplayschess maybe you can take a look at my issue, it's from 1-2 days ago and starts with "404 TRPCError"
thomasplayschess
thomasplayschess13mo ago
Sure! Thank you very much again. And if you want stackoverflow points please answer the question and I will accept it.
thomasplayschess
thomasplayschess13mo ago
WTF, I've used the "mark as solved" on the wrong message (thought it would apply to the whole thread) and now it believes that the emoji is the answer 🤦‍♂️ Well... Looks like I cannot undo my mistake. (just FYI, if anyone comes across this)
funki
funki13mo ago
nah i'm not on that SO grind
thomasplayschess
thomasplayschess13mo ago
Actually, it's really close... But not 100% working as I've only just now noticed. The type hints, etc. is all working great for the input, but the output is always any.
const result = runProcedure(trpc.foo, "aoidwoij");
result.data // type is always any
const result = runProcedure(trpc.foo, "aoidwoij");
result.data // type is always any
Maybe AnyQueryProcedure is not the right type. I will give this another try tomorrow... 🙈
funki
funki13mo ago
oh huh i think it can be fixed will check when i'm home
thomasplayschess
thomasplayschess13mo ago
I got it btw, had to add UseTRPCQueryResult<inferProcedureOutput<QueryProcedure>, TRPCClientErrorLike<QueryProcedure>> as return type. But thanks again for your help. I wouldn't have been able to solve this without your help 🙂
funki
funki13mo ago
nice!
PinkiePie
PinkiePie4mo ago
does anybody know how to solve it with up-to-date trpc?
OreQr
OreQr2mo ago
+1 How to make it work with tRPC v11? I got it to this point but input and output data is any
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type TRPCClientErrorLike } from "@trpc/client"
import {
type DecorateProcedure,
type UseTRPCQueryResult,
} from "@trpc/react-query/shared"
import {
type AnyTRPCQueryProcedure,
type inferProcedureInput,
} from "@trpc/server"

type ExtendedProcedure<T extends AnyTRPCQueryProcedure> = {
_def: {
$values: {
input: T["_def"]["$types"]["input"]
output: T["_def"]["$types"]["output"]
transformer: any
errorShape: any
}
}
} & T

export function useLazySuspenseQuery<
T extends ExtendedProcedure<AnyTRPCQueryProcedure>,
TDef extends T["_def"]["$values"],
>(
path: DecorateProcedure<"query", T["_def"]["$values"]>,
input: inferProcedureInput<T>
): UseTRPCQueryResult<TDef["output"], TRPCClientErrorLike<TDef>> {
return path.useQuery(input)
}
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type TRPCClientErrorLike } from "@trpc/client"
import {
type DecorateProcedure,
type UseTRPCQueryResult,
} from "@trpc/react-query/shared"
import {
type AnyTRPCQueryProcedure,
type inferProcedureInput,
} from "@trpc/server"

type ExtendedProcedure<T extends AnyTRPCQueryProcedure> = {
_def: {
$values: {
input: T["_def"]["$types"]["input"]
output: T["_def"]["$types"]["output"]
transformer: any
errorShape: any
}
}
} & T

export function useLazySuspenseQuery<
T extends ExtendedProcedure<AnyTRPCQueryProcedure>,
TDef extends T["_def"]["$values"],
>(
path: DecorateProcedure<"query", T["_def"]["$values"]>,
input: inferProcedureInput<T>
): UseTRPCQueryResult<TDef["output"], TRPCClientErrorLike<TDef>> {
return path.useQuery(input)
}
Versions: - Typescript: 5.5.3 - tRPC: 11.0.0-rc.477 bump
OreQr
OreQr7d ago
bump