Recommended resources
The freely available resources this lesson will direct you to.
- Tanstack Router Tutorial - Routing, Lazy Loading, Data Fetching, Params...www.youtube.com
- Complete TanStack Router Tutorial - Build Type-Safe React Apps with File-Based Routingwww.youtube.com
- Data Loading | TanStack Router React Docstanstack.com
- Loading Data with TanStack Router: Getting Goingfrontendmasters.com
Hello! Welcome to your ninth lesson in our course on mastering TanStack Start and Router.
In our last lesson, we established the core pattern for server-side data fetching. You learned how to use the loader function to fetch data before a route renders, how to secure API calls using createServerFn, and how to access that typed data in your components with useLoaderData.
This sets the stage for a crucial question: What happens when those data fetches are slow or fail? A robust application must handle these scenarios gracefully.
Lesson 9: Handling Loader States
Today's Goal:
This lesson directly addresses the learning outcome: Handle loader errors and implement pending states for route transitions.
We will build on your knowledge of loaders by exploring the built-in mechanisms TanStack Router provides for managing the entire lifecycle of a data fetch. You'll learn how to give users immediate feedback during slow network requests and how to display helpful error messages instead of a broken page when things go wrong.
What we will cover:
- Pending States: Implementing loading indicators with
pendingComponent. - Controlling UI Flicker: Fine-tuning the user experience with
pendingMsandpendingMinMs. - Error Handling: Catching errors in loaders and displaying them with
errorComponent. - Recovery and Retries: Building a "retry" mechanism for failed loaders.
1. The Three States of Data Loading
When you navigate to a route that has a loader, the data fetching process can be in one of three states:
- Pending: The request has been sent, but the data has not yet arrived.
- Success: The data has been successfully fetched and is available to the component.
- Error: The request failed for some reason (e.g., network error, API error, item not found).
In the last lesson, we focused only on the success state. Today, we'll handle the other two.
The video below provides a concise demonstration of how TanStack Router allows you to define UI for the pending and error states directly within your route definition.
Tanstack Router Tutorial - Routing, Lazy Loading, Data Fetching, Params...
This clip from PedroTech's 'Tanstack Router Tutorial' clearly illustrates the three key properties for handling loader states: component (for success), pendingComponent (for loading), and errorComponent (for failures).
Watch from 24:24 to 25:57. Pay attention to how the createFileRoute object is configured with these three different components and how forcing an error in the loader automatically triggers the errorComponent to render.
As you saw, you can provide a specific component for each state, giving you full control over the user experience during a route transition.
2. Implementing and Controlling Pending States
When a loader is running, especially on a slower connection, leaving the user on the previous page with no feedback can make the application feel unresponsive. The pendingComponent solves this by allowing you to show a loading indicator, like a spinner or a skeleton screen.
Here is a basic implementation:
// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/react-router'
import { fetchPosts } from '../lib/server/posts'
// A simple loading spinner component
function Spinner() {
return <div>Loading...</div>
}
export const Route = createFileRoute('/posts')({
loader: () => fetchPosts(),
// This component will be shown while the loader is running
pendingComponent: Spinner,
component: PostsPage,
})
function PostsPage() {
const posts = Route.useLoaderData()
// ... render posts
}
Avoiding UI Flicker
You might notice that for fast network requests, the pendingComponent doesn't appear at all. This is intentional. TanStack Router waits for a default of 1 second (1000ms) before showing the pending component. This "optimistic" rendering avoids a jarring "flicker" of a loading indicator on fast connections.
You can customize this behavior with two key properties:
pendingMs: The delay in milliseconds before thependingComponentis shown.pendingMinMs: The minimum time thependingComponentwill be shown, even if the data loads immediately after thependingMsthreshold is met. This also helps prevent a jarring flash.
The official documentation and the Frontend Masters blog provide excellent explanations for why and how to configure these timing properties. Please review these sections.
First, read the sections 'Handling Slow Loaders', 'Showing a pending component', and 'Avoiding Pending Component Flash' in the official TanStack Router documentation (resource df0f6). This will give you the technical definition of pendingMs and pendingMinMs.
Loading Data with TanStack Router: Getting Going
Next, read this section from the Frontend Masters blog for a more narrative explanation of the same concepts in a practical context.
Read the subsection 'Loader in a page'. The author provides a clear rationale for using pendingMs to improve the user experience on subsequent navigations.
In summary:
pendingMs: Use this to delay showing a loader. A good starting point is150-200ms. If the data arrives before this, the user sees a seamless transition.pendingMinMs: Use this to ensure that if the loader is shown, it stays for a minimum duration (e.g.,500ms) to give the user time to register what's happening.
You can set these on a per-route basis or configure them globally when you create your router instance in app.tsx.
Complete TanStack Router Tutorial - Build Type-Safe React Apps with File-Based Routing
This short clip demonstrates how to adjust the default pending delay for your entire application.
Watch from 35:06 to 36:09. The presenter shows how to set defaultPendingMs in the createRouter options to make loading states appear instantly for demonstration purposes.
3. Graceful Error Handling
If your loader function throws an error (e.g., an API call fails), TanStack Router will catch it and cancel the navigation. To avoid showing a blank screen, you can provide an errorComponent.
The pattern is simple:
- In your
loader, check for error conditions. - If an error occurs,
throwit. - Define an
errorComponentin your route to render a fallback UI.
// src/routes/posts/$postId.tsx
import { createFileRoute, notFound } from '@tanstack/react-router'
import { fetchPostById } from '../../lib/server/posts'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
try {
const post = await fetchPostById(params.postId)
if (!post) {
// TanStack Router has a built-in error for 404s
throw notFound()
}
return post
} catch (err) {
// For other errors, e.g., API is down
throw new Error('Failed to fetch post.')
}
},
// The error component receives the error that was thrown
errorComponent: ({ error }) => {
// TanStack Router automatically handles `notFound()` errors,
// but you can add custom logic for other error types.
if (error instanceof Error) {
return <div>{error.message}</div>
}
// A good practice is to render the default error component for unhandled cases.
return <ErrorComponent error={error} />
},
component: PostPage,
})
Allowing Users to Retry
A great user experience doesn't just show an error; it provides a path to recovery. The errorComponent receives a function that allows you to reset the error boundary and retry the action.
Data Loading | TanStack Router React Docs
The official documentation details how to create a 'Retry' button. This is a critical pattern for building resilient applications.
Read the section 'Handling Errors with routeOptions.errorComponent'. Focus on the second and third code examples, which demonstrate using the reset prop and, more importantly, using router.invalidate() to re-trigger the failed loader.
The recommended approach is to use router.invalidate(). This tells the router that the data for the current route is stale, which automatically re-runs the loader and resets the error state.
Here's how you'd implement a robust error component with a retry button:
import {
createFileRoute,
useRouter,
ErrorComponent,
} from '@tanstack/react-router'
// ... route definition ...
function MyErrorComponent({ error }: { error: Error }) {
const router = useRouter()
return (
<div>
<p>Something went wrong: {error.message}</p>
<button
onClick={() => {
// Invalidate the route to re-run the loader
router.invalidate()
}}
>
Retry
</button>
</div>
)
}
export const Route = createFileRoute('/posts/$postId')({
// ... loader ...
errorComponent: MyErrorComponent,
// ... component ...
})
Conclusion
You are now equipped to handle the full lifecycle of data loading in TanStack Router. By providing feedback for pending and error states, you can build applications that feel faster, more responsive, and more reliable to your users.
Key Takeaways:
- Use the
pendingComponentoption to show a loading UI during slow route transitions. - Fine-tune the user experience and prevent UI flicker by configuring
pendingMsandpendingMinMsat the route or global level. - Handle loader failures by
throwing an error in theloaderand providing a customerrorComponent. - The
errorComponentreceives theerroras a prop, allowing you to display contextual messages. - Empower users to recover from transient errors by implementing a "Retry" button that calls
router.invalidate().
Next Lesson Preview:
We've learned how to manage slow loads when they happen. But what if we could start loading data before the user even clicks a link? In our next lesson, we will explore a powerful performance optimization: "Implement data preloading on link hover to improve perceived performance."