- Published on
React Query + Hono API + Next.js
- Authors
- Name
- Shelton Ma
- Advanced Server Rendering: using React Query with streaming, Server Components and the Next.js app router.
- React Query vs Axios
QueryClientProvider
1. Setup Install packages
pnpm add @tanstack/react-query
Add provider
// src/components/query-provider.tsx "use client"; // Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top import { isServer, QueryClient, QueryClientProvider, } from "@tanstack/react-query"; function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { // With SSR, we usually want to set some default staleTime // above 0 to avoid refetching immediately on the client staleTime: 60 * 1000, }, }, }); } let browserQueryClient: QueryClient | undefined = undefined; function getQueryClient() { if (isServer) { // Server: always make a new query client return makeQueryClient(); } else { // Browser: make a new query client if we don't already have one // This is very important, so we don't re-make a new client if React // suspends during the initial render. This may not be needed if we // have a suspense boundary BELOW the creation of the query client if (!browserQueryClient) browserQueryClient = makeQueryClient(); return browserQueryClient; } } export default function QueryProviders({ children, }: { children: React.ReactNode; }) { // NOTE: Avoid useState when initializing the query client if you don't // have a suspense boundary between this and the code that may // suspend because React will throw away the client on the initial // render if it suspends and there is no boundary const queryClient = getQueryClient(); return ( <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> ); }
Create providers for more provider
// /src/components/providers.tsx "use client"; import QueryProviders from "./query-provider"; interface ProvidersProps { children: React.ReactNode; } export const Providers = ({ children }: ProvidersProps) => { return <QueryProviders>{children}</QueryProviders>; };
Enable provider in layout
// src/app/layout.tsx import { Providers } from "@/components/providers"; ... <html lang="en"> <body className={`${geistSans.variable} ${geistMono.variable} antialiased`} > <Providers>{children}</Providers> </body> </html> ...
2. Use with Hono API
1. useMutation
Create mutation api
// src/features/conversation/api/use-send-message.tsx import { client } from "@/lib/hono"; import { useMutation } from "@tanstack/react-query"; import { InferRequestType, InferResponseType } from "hono"; import { toast } from "sonner"; export type ResponseType = InferResponseType< typeof client.api.conversation.$post, 200 >; type RequestType = InferRequestType< typeof client.api.conversation.$post >["json"]; export const useSendMessage = () => { const mutation = useMutation<ResponseType, Error, RequestType>({ mutationFn: async (json) => { const response = await client.api.conversation.$post({ json }); if (!response.ok) { throw new Error("Something went wrong"); } return await response.json(); }, onError: () => { toast.error("Failed to send message"); }, }); return mutation; };
useHook in component
// export default function CustomComponent() { ... const mutation = useSendMessage(); const onSubmit1 = async (values: z.infer<typeof formSchema>) => { // Just mutation, can use mutation.isPending // Does not return a Promise, so it can’t be awaited. mutation.mutate( { message: values["prompt"] }, { onSuccess: (data) => { setMessages(data.messages); form.reset(); }, onError: (data) => { console.log(data.message); }, } ); }; const onSubmit2 = async (values: z.infer<typeof formSchema>) => { try { // mutateAsync, can use `const isLoading = form.formState.isSubmitting;` const result = await mutation.mutateAsync({ message: values["prompt"] }) as ApiResponse; if (result.messages) { const typedMessages = result.messages.map((msg) => ({ role: msg.role as "user" | "assistant", content: msg.content })); setMessages(typedMessages); form.reset(); } } catch (error) { console.error("Failed to send message:", error); } }; ... }
2. useQuery
3. useInfiniteQuery
4. useQueryClient
Use
useQueryClient
to ivalidate queries// src/features/projects/api/use-update-project.ts import { useMutation, useQueryClient } from "@tanstack/react-query"; export const useUpdateProject = (id: string) => { const queryClient = useQueryClient(); const mutation = useMutation<ResponseType, Error, RequestType>({ mutationKey: ["project", { id }], mutationFn: async (json) => { const response = ... if (!response.ok) { throw new Error("Failed to update project"); } return await response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"] }); queryClient.invalidateQueries({ queryKey: ["project", { id }] }); }, onError: () => { toast.error("Failed to update project"); }, }); return mutation; };