Setup
Fully typed RPC-like client for React Query v5? Oh, yes!
const {
data, // <- fully typed response data
error, // <- fully typed error data
} = tsr.getPost.useQuery({
queryKey: ['posts'],
queryData: { // <- fully typed request data
params: { id: '1' },
},
staleTime: 1000, // <- react-query options (optional)
});
This documentation is for React Query v5. If you are looking for the v4 docs, please click here.
In order to use the React Query v5 version of @ts-rest/react-query
, make sure you import @ts-rest/react-query/v5
in your code and not @ts-rest/react-query
.
Instructions
1. Installation
- pnpm
- npm
- yarn
pnpm add @ts-rest/react-query @tanstack/react-query@5
npm install @ts-rest/react-query @tanstack/react-query@5
yarn add @ts-rest/react-query @tanstack/react-query@5
2. Setup React Query
If you are not familiar enough with React Query, check out the official @tanstack/react-query
documentation to learn more about it and how to set it up.
Before proceeding, make sure you set up your QueryClientProvider
at the root of your application or layout.
3. Initialize ts-rest React Query
Import your contract, and pass it with the client options to the initTsrReactQuery
function. The client options are the same as the ones you would pass to the initClient
function in @ts-rest/core
.
import { initTsrReactQuery } from '@ts-rest/react-query/v5';
import { getAccessToken } from '@some-auth-lib/sdk';
import { contract } from './contract';
export const tsr = initTsrReactQuery(contract, {
baseUrl: 'http://localhost:3333',
baseHeaders: {
'x-app-source': 'ts-rest',
'x-access-token': () => getAccessToken(),
},
});
initTsrReactQuery
uses our the fetch client under the hood, so you can configure it just as you would the fetch client, including passing a custom API fetcher.
For more information, see the fetch client documentation.
4. Setup ts-rest Provider
Add the tsr.ReactQueryProvider
to your root component, just below the QueryClientProvider
. It is important that it lives as a child of the QueryClientProvider
so that ts-rest can access the query client.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import { tsr } from './tsr';
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<tsr.ReactQueryProvider>{children}</tsr.ReactQueryProvider>
</QueryClientProvider>
);
}
Now use the hooks from tsr
in your components. The structure of the tsr
container follows the same structure as your contract.
Complete Example
import { tsr } from './tsr';
const Posts = () => {
const tsrQueryClient = tsr.useQueryClient();
const { data, isPending } = tsr.posts.get.useQuery({ queryKey: ['posts'] });
const { mutate } = tsr.posts.create.useMutation({
onMutate: (newPost) => {
// get current posts, so we can reset back to it if the mutation fails
const lastGoodKnown = tsrQueryClient.posts.get.getQueryData(['posts']);
// optimistically update the cache with the new post
tsrQueryClient.posts.get.setQueryData(['posts'], (old) => ({
...old,
body: [
...old.body,
{
...newPost.body,
id: `placeholder-${Date.now()}`
}
]
}));
// return the old posts to be stored in mutation context
return { lastGoodKnown };
},
onError: (error, newPost, context) => {
tsrQueryClient.posts.get.setQueryData(['posts'], context.lastGoodKnown);
},
onSettled: () => {
// trigger a refetch regardless of it the mutation was successful or not
tsrQueryClient.invalidateQueries({ queryKey: ['posts'] });
// ^ QueryClient functions that do not consume or provide typed data are not wrapped by ts-rest
// and are provided at the root level only
},
});
if (isPending) {
return <div>Loading...</div>;
}
if (data?.status !== 200) {
return <div>Error</div>;
}
return (
<div>
<ul>
{data.body.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<button onClick={() => mutate({ body: { title: 'Hello World' } })}>Create Post</button>
</div>
);
};
All the hooks have the same function signatures and options exactly as the ones from @tanstack/react-query
, with the only difference being that you need to pass queryData
instead of queryFn
.
When destructing the response from useQuery
or useMutation
, remember that ts-rest returns a status
and body
property, so you'll need to destructure those as well.
Directly Fetching
If you want to make fetch
requests directly without going through React Query, we also expose query
and mutate
functions, so you do not have to initialize a separate fetch client.
// Normal fetch
const { body, status } = await tsr.posts.get.query();
// useQuery hook
const { data, isLoading } = tsr.posts.get.useQuery();