Skip to main content

One post tagged with "React"

View All Tags

· 2 min read

How to use Tanstack Query's useQuery with Remix and Typescript

Using TanStack Query to fetch a resource route in your Remix app?

Want a strongly typed response?

lmaooo

JK here you go:

useQuery<Awaited<ReturnType<Awaited<ReturnType<typeof loader>>['json']>>>

Yay TypeScript!

Want a full example? Surely you're intrigued by now...

import { loader } from '~/routes/your_resource_route'

const {
data,
isLoading,
error,
} = useQuery<Awaited<ReturnType<Awaited<ReturnType<typeof loader>>['json']>>>(
['your_loader_name', queryString],
() =>
fetch(`${window.location.origin}/your_resource_route`).then(
(res) => res.json(),
),
{
enabled: typeof window === 'object', // fetch only on client
},
)

Why does that type work?

To fetch and get the response in JSON format in one line, you do:

await(await fetch('')).json()

And loaders are pretty much just fetch under the hood.

So, to infer the loader's response, we use Awaited<ReturnType<typeof loader>

To infer the .json() part, we use ReturnType<...>['json']>

And finally, because for some reason the json method is async, we wrap everything in a final Awaited<...>

<Awaited<ReturnType<Awaited<ReturnType<typeof loader>>['json']>>>(

Yay!

Running only on the client

The enabled: typeof window === 'object' argument sidesteps all the complex initialData, hydration, and dehydration stuff mentioned in the TanStack Query docs.

Note that it will limit this fetch to the client, so this pattern should only be used for components with complex fetching logic that don't need to render right away.

What about defer?

Why not use the the amazing defer API?

My use case is a complex chart with lots of data to paginate through. Previously I was fetching it in my loader and the page took 4 seconds to paint. Now the page paints in 0.5 seconds.

Also, the pagination makes re-using the page loader a bad idea - if I request a new chunk of data for the chart from the loader (ex. ?chart_page=2), the entire loader would run again, re-rendering the entire route & its children. All I want is the chart to re-render with a new chunk of data, full-stack component style.