Я пытаюсь понять, как использовать интеграцию @tanstack/react-query для tRPC, когда у меня есть условные выборки с обязательными параметрами.
Например, скажем, у меня есть этот бэкэнд:
// Backend
export const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure.input(
z.object({ text: z.string() })
).query((opts) => {
return {
greeting: `hello ${opts.input.text}`,
};
}),
});
Обратите внимание, что параметр text является обязательным.
Теперь предположим, что у меня есть этот интерфейс:
// Frontend
export const trpc = createTRPCReact<typeof appRouter>();
function MyComponent() {
const [text, setText] = useState<string | null>(null)
const {data: helloData} = trpc.hello.useQuery({ text }) // type error
return <>
<div>{helloData.greeting}</div>
<button onClick = {() => setText('a')}>a</button>
<button onClick = {() => setText('b')}>b</button>
<button onClick = {() => setText(null)}>none</button>
</>
}
Здесь text начинается как null, поэтому я не могу вызвать запрос trpc.hello, пока он не получит значение. Итак, Typescript справедливо жалуется:
Type 'string | null' is not assignable to type 'string'.
Type 'null' is not assignable to type 'string'.(2322)
Итак, как мне выполнить этот запрос условно?
Я не могу условно выполнить запрос:
if (text !== null) trpc.hello.useQuery({ text }) // breaks rules of hooks
По правилам крючков.
И это это:
const { data: helloData } = trpc.hello.useQuery(
{ text }, // type error, but works
{ enabled: text !== null }
)
Работает во время выполнения, но ошибка Typescript все еще существует, поскольку он не может точно знать, что объект, как ожидается, будет недействительным, если он не будет использоваться.
Итак, как правильно получить tRPC по условию useQuery?
Полный код с ошибкой типа на машинописной площадке
Стоит отметить, что в tRPC v11 для этого есть новая функция: https://trpc.io/docs/client/react/disabling-queries
Но я пока застрял на v10 из-за других зависимостей, которые еще не совместимы.





Я вижу два пути решения этой проблемы:
Первый — использовать useQuery с условием активации:
const { data: helloData } = trpc.myRoute.hello.useQuery(
{ text },
{
enabled: text !== null,
}
)
Второй — использовать useUtils:
function ComponentWithUseUtils() {
const [text, setText] = useState<string | null>(null)
const [result, setResult] = useState<string | null>(null)
const utils = trpc.useUtils()
const handleQuery = () => {
if (!text) return
utils.myRoute.hello.fetch({ text }).then(data => {
setResult(data.greeting)
})
}
return (
<>
<input type = "text" onChange = {e => setText(e.target.value)} className = "border-1 p-2" />
<button onClick = {handleQuery} className = "bg-red-100 p-2">
Send
</button>
{result}
</>
)
}
см.: https://trpc.io/docs/v10/client/react/useUtils
useQuery — ваш помощник в управлении получением данных с помощью React Query. Он заботится о таких вещах, как поддержание актуальности данных и их автоматическое обновление.
useUtils позволяет вам взять на себя управление получением данных. Это удобно, если вы хотите запускать обновление данных на основе действий пользователя, например нажатий кнопок, а не только при загрузке компонента.
Вот пример ввода первого метода: const { data: helloData } = trpc.project.hello.useQuery({ text } as { text: string }, { Enabled: text !== null, }) useQuery отлично подходит для автоматическая выборка данных, поддержание свежести и синхронизация с сервером. Но он не идеален для всего. useUtils позволяет вам взять на себя управление, когда вам нужно получить данные на основе действий пользователя, например, нажатий кнопок, а не только при загрузке компонента, а также использует ответный запрос под капотом.
для v10 вы можете использовать оператор Bang:
const { data: helloData } = trpc.hello.useQuery(
{ text: text! },
{ enabled: text !== null }
)
или пропишите свою схему в бэкенде, чтобы она тоже принимала null, но потом выдавала ошибку.
Стоит отметить, что в tRPC v11 для этого есть новая функция: https://trpc.io/docs/client/react/disabling-queries
Лучше всего, конечно, обновиться до v11 и использовать skipToken, но вы это уже знаете :)
Ваш первый вариант был рассмотрен в моем вопросе и представляет собой ошибку типа в Typescript, которую, похоже, не существует простого способа решить. А второй вариант означает отказ от всего, что касается реагирования на запросы, верно?