Capítulo 36 de 57

Chapter 36: Data Loading

Core Idea

TanStack Router coordinates per-route data loading via loader functions that run in parallel with route matching, backed by a built-in stale-while-revalidate cache keyed on pathname and loaderDeps, giving apps instant preloads and cached navigation without an external library.

Key Concepts

  • Route loading lifecycle: on each URL change, the router runs route matching (params/search validation) top-down, then beforeLoad serially, then loader (and component.preload) in parallel, with pendingComponent shown if slow and errorComponent shown on failure.
  • loader parameters: receives abortController, cause (enter/preload/stay), context, deps (from loaderDeps), location, params, parentMatchPromise, preload (boolean), and route.
  • loaderDeps: a deterministic, side-effect-free function that extracts specific validated search params to use as part of the loader's cache key; only include what the loader actually uses, including the whole search object causes unnecessary reloads.
  • staleTime / defaultStaleTime: milliseconds a route's loaded data is considered fresh for navigation; defaults to 0 (immediately stale, revalidates in background on reuse). preloadStaleTime defaults to 30 seconds.
  • gcTime / preloadGcTime: retention window (default 5 minutes) before unused cached data becomes eligible for garbage collection.
  • staleReloadMode: 'background' (default, stale-while-revalidate, keeps showing old data while refetching) or 'blocking' (waits for the stale reload before rendering).
  • shouldReload: boolean or function to opt a route out of automatic reloading beyond staleTime/loaderDeps changes, similar to Remix's shouldLoad.
  • Route context (beforeLoad): beforeLoad runs before loader and can return an object merged into the route's context, useful for dependency injection (e.g. injecting a fetchPosts function) down the route tree.
  • useLoaderData(): hook on the Route object (or via getRouteApi) to consume the loader's resolved data in a component.
  • Pending/error components: pendingComponent shows after pendingMs (default 1000ms) and stays at least pendingMinMs (default 500ms) to avoid flicker; errorComponent receives error and a reset() function, and can fall back to the router's default ErrorComponent.

Code Examples

// /routes/posts.tsx
export const Route = createFileRoute('/posts')({
  validateSearch: z.object({ offset: z.number().int().nonnegative().catch(0) }),
  loaderDeps: ({ search: { offset } }) => ({ offset }),
  loader: async ({ deps: { offset } }) => fetchPosts({ offset }),
  staleTime: 10_000,
})
  • What it demonstrates: keying the loader cache on a search param via loaderDeps and setting a custom staleTime.

Key Takeaways

  1. Only put params in loaderDeps that the loader actually consumes, extraneous deps cause unnecessary reloads.
  2. staleTime: Infinity prevents reloads entirely; staleReloadMode: 'blocking' still reloads but waits for it, these are different mechanisms.
  3. Search params are intentionally excluded from the loader's direct arguments; route them through loaderDeps to keep cache keys correct.
  4. Use router.invalidate({ sync: true }) to await invalidation of all matching loader generations; without sync, invalidation happens in the background.
  5. For deeper caching needs (persistence, mutation APIs, shared caching across routes), use an external library like TanStack Query instead of the built-in cache.

Connects To

  • Ch 37: Deferred Data Loading extends this by allowing slow, non-critical promises returned from a loader to stream in after the fast data renders.
  • Ch 38: External Data Loading shows how to replace/coordinate this built-in cache with libraries like TanStack Query.
  • Ch 40: Preloading uses the same staleTime/gcTime machinery but with separate preloadStaleTime/preloadGcTime defaults.