TanStack Router Patterns
It is possible to easily integrate Tanstack Router and Tanstack Query to have access to queryClient inside route loaders. That allows us several features:
- Start fetching data even before a route component begins to render
- Block rendering of a route till data is fetched (to avoid spinnagedon if the response time is quick)
- Co-location of data and routes - goes well with our feature-sliced approach
- Built in suspense and error boundaries - goes well with
useSuspenseQuery
Examples
Section titled “Examples”Start prefetching a query inside a route loader
Section titled “Start prefetching a query inside a route loader”Once resolved, it populates our Query Cache
export const Route = createFileRoute("/_Layout/posts_/$postId")({ loader: ({ context: { queryClient }, params: { postId } }) => { void queryClient.prefetchQuery(postDetailQueryOptions(postId)); // void = we don't care about the data here, we are just triggering the fetch before anything starts to render }, component: RouteComponent,});Fetch data inside a loader
Section titled “Fetch data inside a loader”queryClient.ensureQueryData is a combination of .getQueryData() and .fetchQuery(). It means that it first checks whether a data is already in the Query Cache and if not, it starts fetching the data and returns a Promise.
This is the most common approach we use in route definitions
export const Route = createFileRoute("/_Layout/posts_/$postId")({ component: TestingDeviceDetailPage, loader: ({ context: { queryClient }, params: { postId } }) => queryClient.ensureQueryData(postDetailQueryOptions(postId)), pendingComponent: TestingDeviceDetailPageSkeleton,});As you can see above, we’ve defined a pendingComponent. That means we are creating a Suspense boundary around our route. Whenever we need the data from postDetailQueryOptions in any component in the context of this route, we can just use
const { data } = useSuspenseQuery(postDetailOptions(postId));No need to check for pending state because that is handled by the suspense boundary and the pendingComponent. If we have a global error boundary defined, we don’t need to check for error state either. For that see defaultErrorComponent. .ensureQueryData() and .fetchQuery() both throw on error which means it triggers the closest error boundary and passes the error into it.
Route loading state and UI blocking
Section titled “Route loading state and UI blocking”Tanstack Router is pretty smart about when does it show the pendingComponent. Often times, the API response can take just a small fractions of a seconds. It’s not pleasant UX to flicker loading spinners or skeletons for just a couple of milliseconds. For that reason TS router shows the pendingComponent:
- The pending component is shown only if the API response is taking long (exceeds a threshold, default
1s) - Once the pending component is shown, it stays displayed for a defined time to avoid flashing (default
500ms) - If the threshold is not exceeded, TS router just blocks the UI and keeps showing the previous page
Both these values can be easily configured. To see how and read more about this behavior check Showing a pending component and Avoiding Pending Component Flash sections.