React client
The React client in PTSQ serves as a lightweight wrapper around react-query (opens in a new tab), a powerful library for managing data fetching and state synchronization in React applications.
By leveraging react-query
under the hood, the React client in PTSQ inherits a wide range of features and benefits, including:
-
Data Fetching: Utilizing
react-query
's hooks and utilities, the React client simplifies the process of fetching data from the server and managing the resulting data states. -
Caching and Invalidation:
react-query
provides robust caching mechanisms that help optimize data fetching by storing and managing previously fetched data. Additionally, it supports data invalidation strategies to ensure that cached data remains up-to-date. -
Pagination and Infinite Loading: The React client facilitates pagination and infinite loading patterns with ease, thanks to
react-query
's built-in support for paginated data fetching and automatic triggering of additional data fetches as needed. -
Mutation Handling:
react-query
simplifies the handling of mutations (e.g., updates, creations, deletions) by providing hooks for performing mutations and automatically updating the local cache and UI in response to mutation results. -
Error and Loading State Handling: With
react-query
, the React client efficiently manages error and loading states, making it easy to display loading spinners, error messages, or retry options based on the current data fetching status.
Overall, by leveraging react-query
, the React client in PTSQ offers a robust and efficient solution for managing data fetching and state synchronization in React applications, enabling developers to build powerful and responsive user interfaces with minimal effort.
import { createReactClient } from '@ptsq/react-client';
import type { BaseRouter } from './server';
const client = createReactClient<BaseRouter>({
url: 'http://localhost:4000/ptsq',
});
Query
export const App = () => {
const testQuery = client.test.useQuery({
name: /* string */ 'John',
});
return (
<main>
{testQuery.isFetching ? 'Loading...' : testQuery.data /* string */}
</main>
);
};
Mutation
export const App = () => {
const testMutation = await client.test.useMutation();
return (
<main>
<button onClick={() =>
testMutation.mutate(/* { name: string } */ {
name: 'John'
})
}>
Mutate
</button>
{testMutation.isFetching ? 'Loading...' : testMutation.data /* string */}
</main>
)
}
Query without immediate call
export const App = () => {
const testQuery = await client.test.useQuery({
name: /* string */ 'John',
}, {
enabled: false
});
return (
<main>
<button onClick={() =>
testQuery.refetch()
}>
Query
</button>
{testQuery.isFetching ? 'Loading...' : testQuery.data /* string */}
</main>
)
}
Infinite query
the useInfiniteQuery
is exposed only if your server accepts pageParam
property with any type. That means the input has to extends { pageParam: any }
.
resolver
.args(
Type.Object({
pageParam: Type.Integer(),
}),
)
.output(
Type.Object({
data: Type.Array(Type.String()),
nextCursor: Type.Integer(),
}),
)
.query(({ input }) => {
return {
data: data.slice(
input.pageParam * pageSize,
(input.pageParam + 1) * pageSize,
),
nextCursor: input.pageParam + 1,
};
});
export const App = () => {
const infiniteQuery = client.test.useInfiniteQuery(
{}, // pageParam is automatically passed by the infinite query, you should not have it in the input
{
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
},
);
return (
<div>
<p>data: {JSON.stringify(infiniteQuery.data)}</p>
<button onClick={() => infiniteQuery.fetchNextPage()}>load next</button>
</div>
);
};
Query invalidation
Every query path piece is in the query key, so you can easily invalidate the queries to make them fetch again fresh data from the server.
export const App = () => {
const listPostsQuery = api.post.list.useQuery();
const createPostMutation = api.post.create.useMutation({
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['post'],
/*, ['post', 'list'] will fetch only post.list, ['post'] will refetch every post query (e.g. post.list and post.get) */
});
},
});
return <main>...</main>;
};
React hook form integration
You can simply integrate ptsq hooks with the React hook form (opens in a new tab) library.
import { createPostSchema } from '@/validation';
import type { Static } from '@ptsq/server';
export const App = () => {
const { handleSubmit, control } = useForm<Static<typeof createPostSchema>>({
resolver: typeboxResolver(createPostSchema),
});
const createPostMutation = api.post.create.useMutation({
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['post'],
});
},
});
return (
<main>
<form
onSubmit={handleSubmitCreatePost((data) => {
createPostMutation.mutate(data);
})}
>
<div className="flex flex-col gap-y-2">
<Controller
control={controlCreatePost}
name="title"
defaultValue={''}
render={({ field }) => (
<Input type="text" {...field} placeholder="Title" />
)}
/>
<Controller
control={controlCreatePost}
name="content"
defaultValue={''}
render={({ field }) => (
<Textarea
placeholder="Content"
className="resize-none"
{...field}
/>
)}
/>
<Button type="submit">Submit</Button>
</div>
</form>
</main>
);
};