V10 Client: Move .query
/useQuery
to the front?
#3042
Replies: 16 comments 14 replies
-
Not feeling too strongly about either of the two. However, here are some thoughts I wanted to share:
This is cool, although I think I see people destructuring more based on routers than on operations, but I could be wrong. Could be something that we can ask on twitter maybe? When it comes to this: const postQuery = trpc.useQuery.post.byId({ id: "1" }) I don't find this weird, maybe because I'm a relative beginner in React, but do React people expect more that a hook should be a function instead of an object? Don't really know what React Hooks best practices say about this considering that in this case the actual hook would be |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
-
Just to throw in my 2 cents (For what it's worth!). I'm definitely in favour of moving to the front for ease of use ...
The only other downside in addition to those already mentioned I see is if we only do this on the const utils = trpc.useContext()
utils.post.invalidate() Also - one extra thing to highlight - I'm guessing we would need |
Beta Was this translation helpful? Give feedback.
-
After thinking about it a bit more, I really like the new syntax proposal. In a way it feels "closer to React Query". A huge percentage of the tRPC questions we get on create-t3-app are actually just React Query questions, so any amount of making the RQ docs easier to apply to tRPC is a win to me. // tRPC with this syntax
import { useQuery } from "../util/trpc"; // it would be possible to export like this
function SomeComponent() {
const { data, etc } = useQuery.router.procedure({ foo: "bar" });
return <div>{data?.buzz}</div>
}
// React Query
import { useQuery } from "@tanstack/react-query";
function SomeComponent() {
const { data, etc } = useQuery(['router', 'procedure', { foo: "bar" }]);
return <div>{data?.buzz}</div>
} Disregard my previous post, I'm all for it! |
Beta Was this translation helpful? Give feedback.
-
I have very deep trpc router which I use as a proxy for few external services. None of them has repeatable API, many of them have different purpose, so It's far from simple before
after
Original approach Is more aligned with my way of thinking. Firstly I'm thinking about a namespace and a domain, then about action I want to perform. But tRPC made my life so easy that there is literally zero difference for me. Current syntax is more intuitive imho. edit: Ok I have to admit that new syntax is sexy as well. Getting rid of useQuery before passing arguments to the method might be a strong argument here. |
Beta Was this translation helpful? Give feedback.
-
I'm not the biggest fan of this proposal. To me, it feels quite intuitive that I'm traversing my router and eventually calling a method (i.e. the hook) on a procedure. Even more so when the backend router now really is an object. Say I have this router const router({
user: router({
byId: publicProcedure...
list: publicProcedure...
})
}) I would go down the router until I get to the target procedure, and then eventually calling the desired method on it, e.g. const user = trpc.user.byId
// i have now traversed the router object, lets call the method on it
.useQuery()
// or for the utils stuff
.invalidate() To me, this intuitiveness disappears when the order is switched, especially since we're now never calling a hook, we just use the hook name as some sort of object attribute which feels even stranger when you think of rules of hook and that hooks are not supposed to be conditionally called - well now we're never calling a hook? I get that it's "more similar to React Query", but I don't see that as a problem. If you want it at the front for those purposes, I like #2316 more. |
Beta Was this translation helpful? Give feedback.
-
Another benefit to this syntax is it makes leveraging auto complete more intuitive (which IMO is a one of the major factors in what makes tRPC so valuable to developers). In practice with V9, it's very common for me to begin typing Having that differentiation up front means it narrows down the end points and should make it easier to find the end point the dev is looking for quickly, theoretically halving the number of end points to scroll through and preventing situations where the dev confuses a query end point with a mutation end point. This makes finding the endpoint via autocomplete much, much easier. With the current V10 syntax, we're always having to scroll through all of the query and mutation end points even when we already know we're looking for a mutation. Visually that's just going to be a lot harder to process and more difficult to find the endpoint we want. The syntax switch would have a significant positive impact on the usability of the in-editor autocomplete and for that reason alone I think it's better. |
Beta Was this translation helpful? Give feedback.
-
I wasn't sold at first, but after giving it a little think, the benefits are pretty good. Obviously autocomplete becomes less cluttered and top level methods that don't require scope data become a possibility. I think giving up a little consistency is worth it |
Beta Was this translation helpful? Give feedback.
-
I am not (initially) in favor of this proposal, for me it’s intuitive that for each api call I make, I am getting the same “output” as far as what I can use. .useQuery() at the ends tells me what kind of things I can extract, and if it switched to I typically reformat my calls to something like If I saw .getAll(), I might mistakenly assume it returns the complete list of users. For me personally, if I’m reading code quickly, I would start at the end of the line since that’s what ultimately giving me the return. edit: of course, given some time to get used to it, I could see myself abstracting the call away into |
Beta Was this translation helpful? Give feedback.
-
Not a fan of this proposal as I don't think think that this meaningfully improves the ergonomics of the API. I think that the "lexical" changes with this proposal break down the deeper that routers are nested as well. The "traditional" art of programming I'm used to, or that I would expect, with a fully typed system like this is to traverse the various options defined in my schema of routes/procedures and then the methods (queries, mutations, etc) are consequently revealed to me. Prisma is a really great example of this: prisma.user.findUniqueOrThrow(...)
The con of "Misalignment between backend and frontend" is further compounded by using tools like Prisma in your procedures because it moves away from a sense of consistency that the tRPC client already has with it. I would imagine many users of tRPC would also use Prisma. The changes in this proposal fundamentally break what I would consider to be the most familiar way to traverse the available methods of a client. A separate issue is one of discoverability. I understand the appeal of defining your intentions up front but that might present some challenges for an engineer being onboarded to a project where the entire schema is not presented up front by the client. For example: trpc.useQuery.post.byId(...) ☝🏼 My understanding with these changes is that by defining trpc.post.byId.useQuery(...) ☝🏼 Even if don't use any other procedures under I will say one of the pros mentioned in this RFC is nice in that we would have access to more methods straight off of the |
Beta Was this translation helpful? Give feedback.
-
Haven't seen anyone else commenting on this part so I thought I'd jump on it. I think this is quite a big deal actually. To keep the API intuitive and uniform these sorts of things can confuse newcomers quite a bit? If I for example can chain inputs in a "forward fashion", the middleware is chained before the resolver function and then finally the query/mutation. Then, on the frontend we flip it around and the part that we'd think is the function (query(() => ...) is just accessed like an object attribute 🤔. |
Beta Was this translation helpful? Give feedback.
-
Sorry, I'm against for a couple of reasons. The cons look fundamental and scary but the pros seem cosmetic. Personally, I like Is there another hook library which has I don't see the operation first autocompletion argument. You can already do it - just arrange your router in that way yourself. (I admit that would be manual work and have the We have Whilst it would be nice not to have to write useQuery() at the end, at least it's very flexible to new additions (on an endpoint by endpoint basis too). My biggest trpc API gripe is where I think all the endpoint operations in the same place. I'd even be happy with With Next 13/React 18, perhaps trpc will change too - more (server side component) code looking like It seems premature to change the v10 API now, when there's a big stuff happening in the wider ecosystem which might result in more fundamental changes later. |
Beta Was this translation helpful? Give feedback.
-
Curious about this one -- is there a difference of opinion between i.e. both of these could be true:
We don't have to have the react package exactly map the client package. |
Beta Was this translation helpful? Give feedback.
-
For "raw" it really seems logical and good I'm feeling and seeing this RFC as con in general.
So no, to me it's hard No. I love it they way it is in v10 and thinking to go hard on tRPC. |
Beta Was this translation helpful? Give feedback.
-
Coming with the perspective of a long time react person, hooks must be functions. I've seen in the issues trpc is already asking for and blocked by changes to the linting rules for v10, and I suspect going down this road will lead to the react team shutting down any discussion of supporting this API via lint rules. It's just not how hooks are intended to be. Anyway, to be part of the solution: Perhaps a way to go which would be supported by the existing lint rules, and also solve this problem would be like: const { useQuery, useMutation, useInfiniteQuery } = trpcClient
// React
const postQuery = useQuery(api => api.post.byId(params), { /* react-query opts */ })
const postListQuery = useInfiniteQuery(api => api.post.byId(params), { /* react-query opts */ })
const addPostMutation = useMutation(api => api.post.add, { /* react-query opts */ })
// then: `addPostMutation.mutate({ title: "hello world" }); Lots of ways this could be implemented under the hood, including just wrapping the core of this RFC which would make the react-query portion a pretty skinny shim, or doing something more clever because of course query-keys need to be generated properly. I'm pretty sure it could be made to work and keep the performance benefits of V10, while inferring the type of useQuery or useMutation based on the wrapped selection. |
Beta Was this translation helpful? Give feedback.
-
We've decided not to proceed with this at the moment. If we do revisit it in the future we will provide a codemod to do the change automatically. |
Beta Was this translation helpful? Give feedback.
-
Resolved: #3042 (comment)
Problem / background
After using tRPC 10 for a while now, I've warmed up to the idea of flipping the procedure type to the beginning of the statements in the client.
I find myself writing
trpc.post.byId()
often instead oftrpc.post.byId.useQuery()
, and I'm one of the authors of this thing.Current
Proposal - move
.query
/.mutation
to the frontMotivation
Pros
trpc.post.byId()
myself sometimesconst {query, mutation} = trpc
and use that easilytrpc
-client object that won't interfere with actual routers/proceduresCons
Misalignment between backend and frontend:
In the backend, we add procedures in the
procedure.input(y).query(() => '...')
-fashion.useQuery
/useMutation
looks like a hook but is an objectMight lead to same situations where people try to call
useQuery()
instead ofuseQuery.post.byId()
as it looks like a hook.Standard linters in React
useQuery.post.byId()
will not be interpreted as a hook in the React linter, but neither doespost.byId.useQuery()
currently - facebook/react#25065Additional information
Beta Was this translation helpful? Give feedback.
All reactions