skip to Main Content

I’m trying to understand how to use the @tanstack/react-query integration for tRPC when I have conditional fetches that have required parameters.

For example, say I have this backend:

// 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}`,
    };
  }),
});

Note that the text parameter is required.

Now let’s say I have this frontend:

// 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>
  </>
}

Here text starts as null, and so I cannot call fetch the trpc.hello query until it has a value. So Typescript rightly complains:

Type 'string | null' is not assignable to type 'string'.
  Type 'null' is not assignable to type 'string'.(2322)

So how to do I run this query conditionally?

I can’t conditionally run the query:

if (text !== null) trpc.hello.useQuery({ text }) // breaks rules of hooks

Due to the rules of hooks.

And this this:

const { data: helloData } = trpc.hello.useQuery(
  { text }, // type error, but works
  { enabled: text !== null }
)

Works at runtime, but the Typescript error is still there since it can’t really know that the object is expected to be invalid when it won’t be used.

So what is the right way to conditionally fetch a tRPC useQuery?

Full code with type error in the typescript playground


It’s worth noting that tRPC v11 has a new feature for this: https://trpc.io/docs/client/react/disabling-queries

But I’m stuck on v10 for the time being due to other dependencies which are not yet compatible.

2

Answers


  1. I see two ways to solve this:

    The first one is to use useQuery with an activation condition:

    const { data: helloData } = trpc.myRoute.hello.useQuery(
    { text },
    {
        enabled: text !== null,
    }
    )
    

    The second one is to use 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}
            </>
        )
    }
    

    see: https://trpc.io/docs/v10/client/react/useUtils

    useQuery is your go-to for managing data fetching with React Query. It takes care of things like keeping data fresh and updating automatically.

    useUtils lets you take the wheel on data fetching. This is handy when you want to trigger data updates based on user actions, like button clicks, instead of just when a component loads.

    Login or Signup to reply.
  2. for v10, you can use the bang operator:

    const { data: helloData } = trpc.hello.useQuery(
      { text: text! },
      { enabled: text !== null }
    )
    

    or write your schema in the backend so that it also accepts null, but then throws an error.

    It’s worth noting that tRPC v11 has a new feature for this: https://trpc.io/docs/client/react/disabling-queries

    The best thing of course is to update to v11 and use the skipToken, but you already know that 🙂

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search