Сервер готов, осталось подключить интерфейс. Нужно показывать «загрузку» и ошибки, не гонять одни и те же запросы по сто раз и обновлять список после добавления заметки. Обо всём этом уже заботится TanStack Query, а tRPC аккуратно соединяет его с вашим роутером — с полными типами. В этом уроке мы подключим клиент к Next.js App Router, научимся читать и менять данные, делать оптимистичные обновления и вызывать процедуры прямо на сервере.
Как устроена интеграция
В первом уроке мы вызывали процедуры «напрямую»: trpc.note.list.query(). Это обычный клиент — он умеет отправить запрос и вернуть результат, но не знает ни про кэш, ни про состояние загрузки. Для React есть пакет @trpc/tanstack-react-query, и работает он иначе: не подменяет TanStack Query, а готовит для него настройки.
Аналогия: tRPC — это «рецепт» запроса (адрес, входные данные, тип результата), а TanStack Query — «повар», который по рецепту готовит: кэширует, повторяет при сбое, показывает состояние загрузки. Рецепт возвращают методы queryOptions() и mutationOptions(), а повару вы отдаёте его сами — в обычные useQuery и useMutation.
| В tRPC | В TanStack Query | Для чего |
|---|---|---|
trpc.x.queryOptions() | useQuery | чтение данных |
trpc.x.mutationOptions() | useMutation | изменение данных |
trpc.x.queryFilter() | invalidateQueries | сбросить кэш, чтобы данные загрузились заново |
trpc.x.queryKey() | getQueryData, setQueryData | править кэш вручную |
Подключение: провайдер
Нужно создать два объекта — клиент TanStack Query (QueryClient) и клиент tRPC — и передать их в провайдер. Хуки и провайдер создаёт функция createTRPCContext:
// lib/trpc.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import { createTRPCContext } from '@trpc/tanstack-react-query';
import { useState } from 'react';
import type { AppRouter } from '@/server/routers/_app';
export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();
export function TRPCReactProvider(props: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: '/api/trpc' })],
}),
);
return (
<QueryClientProvider client={queryClient}>
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
{props.children}
</TRPCProvider>
</QueryClientProvider>
);
}Что здесь важно:
'use client'обязателен: провайдер и хуки работают только в клиентских компонентах.createTRPCContext<AppRouter>()возвращаетTRPCProviderи хукuseTRPC. ТипAppRouter— тот самый, который сервер экспортировал в прошлом уроке.- Оба клиента создаются через
useState(() => ...), чтобы не пересоздаваться при каждом рендере: иначе кэш обнулялся бы. urlдолжен совпадать сendpointв route handler. Если на сервере включёнtransformer: superjson, его нужно добавить и вhttpBatchLink.
Осталось обернуть приложение в app/layout.tsx: