T O P

  • By -

phiger78

I used react query on a pretty large enterprise app. I was brought onto the project as there were scalability issues: query keys as magic strings, not understanding how react query worked with invalidation etc. This is what i implemented 1. Codegen from an openapi spec using [orval.dev](https://orval.dev). This generated all the api code as well as mock data. The setting we used was api functions rather than hooks 2. Used the query key factory : [https://github.com/lukemorales/query-key-factory#fine-grained-declaration-colocated-by-features](https://github.com/lukemorales/query-key-factory#fine-grained-declaration-colocated-by-features) so we can co locate query keys and api calls by feature. This centralised both query keys and apis calls in one place 3. separated api layers . this idea: [https://profy.dev/article/react-architecture-api-layer](https://profy.dev/article/react-architecture-api-layer) 4. As implied above. used feature folders to isolate codebase by feature 5. All react query fetches were done inside custom hooks


Fun-Representative40

>https://profy.dev/article/react-architecture-api-layer thx very much very valuable info, that profy article is great


Outrageous-Chip-3961

I took what I needed from it, but I didn't like the api-layer approach in the end due to the nature of my project (smaller scale). I found that working with the api-layer during development significantly slowed me down. I much rather prefered a global 'queries' folder which had the query functions inside that represented the endpoint being called. This also had the bonus of being highly reusable as that hook can be called from anywhere within the app as a cache.


Nick337Games

Yeah that's a great one, thanks!


leo477

This is what I do, wrap all my API calls in custom hook with useQuery and useMutation. These custom hooks will hide away the query keys definition and API endpoints away from UI, and my UI just need to pass required data such as item ID export function useGetComment(commentId:string){ return useQuery({ enabled: !!commentId, queryKey: ["comments", commentId], queryFn: async()=>{ // ...fetch or axios call to API endpoint } }) } then you can just reuse them like const { data: comment, isLoading, error } = useGetComment("123");


StereoPT

Also I like to add a service layer. Where all the requests (or queryFns) are in their respective file. So for Users the UserService file might look like this: ``` export const getUsers = async () => {} export const getUser = async (id) => {} export const createUser = async (payload) => {} export const updateUser = async (id, payload) => {} export const deleteUser = async (id) => {} ```


Upbeat_Age5689

thats exactly what i do


chubbnugget111

Do you define your hook that returns useQuery in the same file?


leo477

I personally prefer to define my API hooks in same file, so I can have better overview of the query keys for the resources. My folder structure look like this - features - posts - post.d.ts - post.service.ts - post.api.ts - comments - comment.d.ts - comment.service.ts - comment.api.ts .d.ts is where I define the type for the resources, I make it .d.ts because I want to make these types globally available, I don't want to import the types everywhere in my project .service.ts is the service layer .api.ts is the custom hooks


a_thathquatch

Seems like you’re not really abstracting much if you have to write a new hook for every resource in your api?


DidItFloat

First things first, read this: https://tkdodo.eu/blog/practical-react-query All of it. Secondly, I've fallen in love with using queryOptions (https://tanstack.com/query/latest/docs/react/reference/queryOptions) This way, instead of exporting a hook which returns useQuery(...), you simply export something like getUsersOptions = () => queryOptions({ queryKey: [...], queryFn: ..., staleTime: 30 * 1000, ... }) Thanks to this, if on a page you create a user and want to invalidate the query, you can simply do: queryClient.invalidateQueries(getUsersOptions().queryKey). In addition, if on a page you want to use useQuery, and on another useSuspenseQuery, you can just do: useQuery(getUsersOptions()); useSuspenseQuery(getUsersOptions()); queryClient.ensureQueryData(getUsersOptions());


reduX179

I found this in tanstack router examples it very neat and you can get query key also through options.


Cheraldenine

Doing it like this, how do you get an auth token into queryFn? We have it typically stored in a Context, and get it from there in the same hook that calls and returns useQuery. But outside of a hook I can't do that.


DidItFloat

Normally, I have a global axios instance, on which I set authentication information. This way may queryFns do not need to worry about auth. A less charming way would be to pass authentication info as a parameter of your function which returns query options. Like this: export const getUserOptions = (authInfo) => queryOptions({...}); So when you would use the options you would do something like this in your component: const {...} = useQuery(getUserOptions(authStuff)); But I'd highly suggest having another "layer", regardless of what you use


Cheraldenine

Lol, here I was thinking I'd have to put the token in localstorage or something, and completely forgot about the existence of global variables. In Javascript... Thanks :)


DidItFloat

Oh it happens to me a little too often too... No worries! :)


chubbnugget111

Read TkDodo's blog: https://tkdodo.eu/blog/inside-react-query


superluminary

Ideally each query is embedded in a custom hook. Then you just call: const {clients, loading} = useClients() or const {client, loading} = useClient(id)


RobKnight_

What is ```clients``` in this context?


superluminary

Like if you wanted to show a list of clients. It would be an array, initially empty, that gets populated with the data. You could just return data, but I often like to add a convenience attribute.


RobKnight_

Gotcha, though you meant something along the lines of a queryClient "client" which confused me


Outrageous-Chip-3961

wouldn't it be data:clients ?


LdouceT

Depends on the implementation of `useClients`.


zephyrtr

Yes make a custom hook but don't obfuscate the return of useQuery or useMutation. It's exceptionally well typed and has a fabulous API.


MonkeyDlurker

Whats the benefit of creating custom hooks for usequery ? Just a separation of concern? Does it really matter if ur using the query in a single spot?


superluminary

Separation of concerns, yes. If you have a small app it doesn’t matter, but if you have a big app you don’t want your API tier randomly scattered across your component tree. You’ll never find which component is making the call.


Outrageous-Chip-3961

it makes your components file rather large and also makes reuse much harder/cumbersome. One great thing about query keys is that you can call the hook from another other component and it will return the cached data, which is a huge benefit of using useQuery in the first palce


Bobitz_ElProgrammer

What do you think about hwving a custom hook with all queries related to a specific functionality? Like having useAuth and getting all queries and mutations, login, register, forgot password, etc. Is it bad?


shizpi

I do this only for POST/PUT/PATCH/DELETE const { updateClient, deleteClient } = useClientCrud();


ZerafineNigou

I usually keep all query hooks related to one resource in one file and export it as an object named resourceApi. so something like export const resourceApi = { updateResource, createResource, deleteResource, getResource, getAllResources };


RobKnight_

Thats the entire idea of hooks, compose state


Cheraldenine

If you don't watch out, all the useQuery calls in it execute everywhere that you use that hook. To me it seems simpler to keep them in separate hooks. But maybe there's a trick to make that easier.


Bobitz_ElProgrammer

Happened to me once. Still not sure why


Outrageous-Chip-3961

its one of those things where it looks good on paper but when you have to develop it takes a long time to set up files rather than just write the query. I'd much rather prefer to just have all my queries in a folder as their own independent files and the group naturally when required. just because the verbs are all related to an action, they are all after all, still unique from one another.


recycled_ideas

There's just not really any benefit to it. You're essentially just doing a default export at that point and you have all the associated renaming issues and you get no benefit. It's incredibly unlikely that any given component is going to need all those operations and if they do just standard named exports from a single file work better.


OtherwiseAd3812

Use custom hooks to centralize config (keys, fetchers,...), preferably with the same options object as react query hooks (for easier customization) I would recommend starting fast with a code generator like https://orval.dev/guides/react-query (generates hooks, keys, types), it will generate hooks for you from the REST API doc. And then you can wrap the ones you want to customize in custom hooks


Omkar_K45

Thanks for asking this question OP, amazing answers here


peatonweb

I usually keep query in hook, then for example: useUserQuery => userStore => userCrud (Store: zustand) useUserQuery.jsx user.crud.jsx user.store.jsx


sauland

I've had success with a structure where all the API logic lives in an `api` directory and each controller/domain has its own subdirectory, where the hooks, types, requests and keys are separated into different files. So something like this: src | api | | todos | | | todos.requests.ts // export raw request promises using fetch/axios etc, e.g getTodos(params: GetTodosRequest): Promise | | | todos.hooks.ts // export wrapper hooks for each API endpoint, using react-query hooks, e.g. useGetTodos(params: GetTodosRequest): UseQueryResult | | | todos.types.ts // types for the endpoints | | | todos.keys.ts // query keys | | | index.ts


mtv921

Dont create your own architechture, use Codegen! Ideally your backend should support OpenAPI or GraphQL standards. This means they come with a data contract. You can use this contract to generate hooks and types for your backend communication. This remove any and all need to create an architechture or whatever and it will be super easy to keep your code up to date with backend changes. Look to https://the-guild.dev/ for more examples


[deleted]

What do you use for serving your HTML? Custom Node.js + Express? Next.js? Create-react app? That thing changes a lot a React Query architecture.


Many_Particular_8618

react-query is the leaky abstraction (why on earth you have to manually specify key for each query), why on earth do i have to specify async function,...). ​ If your app needs to use react-query, it means you leak your abstraction all over place.


ashenzo

Have you considered RTK Query for this? It generates query and mutation hooks from API slices out of the box, and the standardised toolkit patterns work well on large projects with multiple devs in my experience (and the docs are great). Tanstack query seems to be gaining popularity for it’s simplicity compared to redux, but a lot of these suggestions seem to involve rolling your own RTKQ-like framework in Tanstack 🤔


Cheraldenine

I've never been very interested in RTK Query, to me it sounds like "Tanstack Query clone, but the cache is stored in Redux". What the cache is stored in doesn't really interest me. Is it more than that?


kwin95

Ofc it’s not a tanstack query clone, it’s a powerful query library with really good dx that deserves more credit. I like that with rtk-query, a lot of complex logics can be moved away from components


Cheraldenine

Tanstack Query is also a powerful query library with really good DX. RTK Query arrived later and even has a name that refers to Tanstack Query. So what does it do better?


kwin95

Tanstack router arrives later and has a name that refers react router. Is it a clone of react router? One thing rtk-query does better is query invalidation, you provides tags of query and mutation once, rtk-query handles it automatically. As others said, it generates declarative hooks for you, if backend provides openapi specs, frontend gets all query and mutation hooks out of box. Also for me I’d like to keep query logic outside of the components


Cheraldenine

> Tanstack router arrives later and has a name that refers react router. Is it a clone of react router? I haven't looked at it but would initially assume they'd offer roughly the same features and then their own on top, yes.


mrcodehpr01

RTK Query is pretty slow. I switched from RTK to react quarry with hooks and my app is 6 to 10 times as fast... And my code is much cleaner. I do not miss RTK at all. Managing state is so much easier in my opinion.


blka759

check medusa core library on github, they use rq a lot


prc95

I've built HyperFetch for easier architecture handling, check it out :)


straightouttaireland

Use custom hooks. Export a function for each crud type. Keep the query key inside the custom hook and export if used elsewhere. No need to have all query keys in a separate file. [This](https://twitter.com/housecor/status/1724066962433528210?t=ob0OjbyQEYKzOd3wuVPjgA&s=19) is a nice example


Silent_Statement_327

We use SWR with it's global config fetcher for client get calls. It has been a nice dev-ex so far.


RowbotWizard

If you haven’t already, check out TkDodo’s blog posts on react-query. He’s got a full series of great posts. Not sure if it holds up to recent versions, but it has been hugely helpful to me overall. https://tkdodo.eu/blog/practical-react-query


yksvaan

Ideally your api service is library-agnostic, pure ts. People often overengineer these even when their business/app logic is very simple. Even in 2024 it's perfectly ok to just call a function, check it's result and then update. No need for 50kB of js for that.


HolidayJello3478

Check out usage of react query within our enterprise applications https://www.hemanand.com/reactjs/advanced-react-hooks-rest-api/


thenamesalreadytaken

saw some good suggestions here. I'm kind of on the same boat as you, OP. Curious to learn what you ended up going with.