Skip to content

Commit b2bf874

Browse files
authored
feat: react-query typed QueryClient function wrappers (#216)
* feat: react-query typed QueryClient function wrappers * feat: change API for query client functions * fix: errors
1 parent 2517591 commit b2bf874

12 files changed

Lines changed: 701 additions & 242 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@ts-rest/core': patch
3+
'@ts-rest/react-query': patch
4+
'@ts-rest/solid-query': patch
5+
---
6+
7+
Add rawQuery parameter to custom API fetcher parameters

.changeset/slow-kangaroos-act.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@ts-rest/core': minor
3+
'@ts-rest/react-query': minor
4+
'@ts-rest/solid-query': minor
5+
---
6+
7+
Added React Query QueryClient function helpers

apps/docs/docs/react-query.mdx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,76 @@ return (
200200
</div>
201201
);
202202
```
203+
204+
## QueryClient Helpers
205+
206+
In addition to the hooks provided, `@ts-rest/react-query` also provides a custom hook `useTsRestQueryClient` with wrapper functions around some `QueryClient` functions to help invoke the API as well as provide some typing.
207+
208+
```tsx
209+
import { initQueryClient, useTsRestQueryClient } from '@ts-rest/react-query';
210+
211+
export const client = initQueryClient(router);
212+
213+
const App = () => {
214+
const apiQueryClient = useTsRestQueryClient(client);
215+
216+
// You can either use apiQueryClient or client to call useQuery, useMutation, etc.
217+
const { data, isLoading, error } = apiQueryClient.posts.get.useQuery(['posts']);
218+
const { mutate, isLoading } = client.posts.create.useMutation();
219+
220+
const createPost = async () => {
221+
return mutate({ body: { title: 'Hello World' } }, {
222+
onSuccess: async (data) => {
223+
// this is typed ^
224+
apiQueryClient.posts.get.setQueryData(['posts'], (oldPosts) => {
225+
// this is also typed ^
226+
return {
227+
...oldPosts,
228+
body: [...oldPosts.body, data.body],
229+
}
230+
});
231+
}
232+
});
233+
}
234+
235+
if (isLoading) {
236+
return <div>Loading...</div>;
237+
}
238+
239+
if (data?.status !== 200) {
240+
return <div>Error</div>;
241+
}
242+
243+
return (
244+
<div>
245+
<button onClick={createPost}>Create Post</button>
246+
{data.body.map((post) => (
247+
<p key={post.id}>post.title</p>
248+
))}
249+
</div>
250+
);
251+
};
252+
```
253+
254+
#### Functions
255+
256+
Following the same design philosophy as the hooks, these helpers follow the original `QueryClient` function APIs as closely as possible.
257+
Essentially, the API is exactly the same, but instead of passing a `queryFn`, you pass the request parameters directly.
258+
259+
- `async fetchQuery(queryKey, args, options) => Promise<SuccessResponse>`
260+
- `async fetchInfiniteQuery(queryKey, argsMapper, options) => Promise<InfiniteData<SuccessResponse>>`
261+
- `async prefetchQuery(queryKey, args, options) => Promise<void>`
262+
- `async prefetchInfiniteQuery(queryKey, argsMapper, options) => Promise<void>`
263+
- `getQueryData(queryKey, filters) => SuccessResponse | undefined`
264+
- `async ensureQueryData(queryKey, args, options) => Promise<SuccessResponse>`
265+
- `getQueriesData(filters) => [QueryKey, SuccessResponse | undefined][]`
266+
- `setQueryData(queryKey, updater) => SuccessResponse | undefined`
267+
268+
Functions such as `invalidateQueries` that neither exchange typed data nor invoke the API have no benefit of being wrapped. Therefore, call these functions directly through your `QueryClient` instance.
269+
270+
:::info
271+
272+
Functions that may throw on failure such as `fetchQuery` will throw an error if the request fails or returns a non-2xx response.
273+
Be sure to handle these errors appropriately.
274+
275+
:::

0 commit comments

Comments
 (0)