thomasplayschess
thomasplayschess15mo 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 ❤️
40 Replies
function
function15mo 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
thomasplayschessOP15mo 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.
function
function15mo 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
thomasplayschessOP15mo ago
Oh, okay. My bad. You are right. Yes, sorry, wasn't sure on the wording.
function
function15mo 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
thomasplayschessOP15mo ago
TrpcProcedure is the one that I made up in the stackoverflow question.
function
function15mo ago
oh
thomasplayschess
thomasplayschessOP15mo ago
For example this one: type TrpcProcedure = (typeof trpc)[keyof AppRouter['_def']['procedures']]; (which doesn't work)
function
function15mo ago
you wanna dive into tRPC's types. ctrl-click an existing, working useQuery it might be scary tho
thomasplayschess
thomasplayschessOP15mo 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 🙈
function
function15mo 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
thomasplayschessOP15mo ago
As you said, it's super scary...
function
function15mo ago
have you tried AnyQueryProcedure? i just looked around the tRPC docs and searched for "types" and eventually came across that.
thomasplayschess
thomasplayschessOP15mo 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).
function
function15mo 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
thomasplayschessOP15mo 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.
function
function15mo ago
no, that one ain't it because it's literally typed with any, but it's definition is a lead
thomasplayschess
thomasplayschessOP15mo 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
function
function15mo ago
okay seems like my instinct was wrong and you're already on the right track with DecorateProcedure
thomasplayschess
thomasplayschessOP15mo ago
At least I got that right... But no clue how I make DecorateProcedure<TProcedureOrRouter, TFlags, TPath> generic now 🙈
function
function15mo 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
thomasplayschessOP15mo ago
AWESOME, I'll try it 🥳 YESSS
function
function15mo ago
@thomasplayschess maybe you can take a look at my issue, it's from 1-2 days ago and starts with "404 TRPCError"
thomasplayschess
thomasplayschessOP15mo ago
Sure! Thank you very much again. And if you want stackoverflow points please answer the question and I will accept it.
thomasplayschess
thomasplayschessOP15mo 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)
function
function15mo ago
nah i'm not on that SO grind
thomasplayschess
thomasplayschessOP15mo 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... 🙈
function
function15mo ago
oh huh i think it can be fixed will check when i'm home
thomasplayschess
thomasplayschessOP15mo 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 🙂
function
function15mo ago
nice!
PinkiePie
PinkiePie6mo ago
does anybody know how to solve it with up-to-date trpc?
OreQr
OreQr3mo 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
OreQr2mo ago
bump bump
Morley
Morley4w ago
Trying to bring some awareness to this, as it's super frustrating that the needed types were previously exposed: https://github.com/trpc/trpc/discussions/6171
Alex / KATT 🐱
try a bounty? create a feature request -> use the polar thing
MlNl-PEKKA
MlNl-PEKKA3w ago
I've made this specifically for useQuery as the title says, let me know if this isn't what you asked for: https://github.com/trpc/trpc/issues/6180#issuecomment-2452664657 Can I know why, this pattern is even used/needed? Why couldn't we just use the useQuery function, provided directly from the nested trpc object. Just curious.
Morley
Morley3w ago
Thanks! That's exactly what I was looking for. Basically the use-case is a generator of sorts that takes in a query procedure and spits out some DataTable components that are bound to those types. Much appreciated.