Capítulo 39 de 57

Chapter 39: Data Mutations

Core Idea

TanStack Router deliberately does not manage mutation/submission state itself; its role is limited to invalidating loader data and reacting to URL side effects after a mutation, so mutation state should be owned by a dedicated library (TanStack Query, SWR, RTK Query, Redux, etc.) and coordinated with the router via router.invalidate() and router.subscribe().

Key Concepts

  • What to look for in a mutation library: submission state caching, local/global optimistic UI support, built-in invalidation hooks, support for multiple in-flight mutations, globally accessible mutation state, and submission history/garbage collection.
  • router.invalidate(): invalidates committed, cached, and in-flight loader generations matching the current state; retires matching active preload lanes and reloads current active matches through the normal loading protocol. By default this is non-blocking (stale data revalidates in the background).
  • router.invalidate({ sync: true }): awaits the invalidation until all affected loaders have finished reloading, useful when you need to guarantee fresh data before proceeding (e.g. before a redirect).
  • Long-term mutation state problem: without explicit cleanup, mutation state (e.g. a "Post updated successfully" message) can persist and reappear inappropriately when a user navigates away and back to the same route.
  • Mutation keys: the preferred solution when a mutation library supports keying (e.g. key: ['sendMessage', roomId]), so mutation state automatically resets when the key (like a route param) changes.
  • router.subscribe('onResolved', callback): fallback solution for libraries without a keying mechanism; the onResolved event fires when the location path changes (not just reloads) and has fully resolved, a good place to manually clear stale mutation caches.

Code Examples

const router = useRouter()

const addTodo = async (todo: Todo) => {
  try {
    await api.addTodo()
    await router.invalidate({ sync: true })
  } catch {
    // handle error
  }
}
  • What it demonstrates: invalidating router loader data synchronously after a successful mutation.
const router = createRouter()
const coolMutationCache = createCoolMutationCache()

const unsubscribeFn = router.subscribe('onResolved', () => {
  coolMutationCache.clear()
})
  • What it demonstrates: clearing stale mutation state when the route changes, for libraries lacking a native keying mechanism.

Key Takeaways

  1. Pick a dedicated mutation library rather than expecting TanStack Router to manage submission/optimistic state.
  2. Always call router.invalidate() after a mutation that affects loader data; use { sync: true } when you need to wait for the reload before continuing.
  3. Prefer mutation-library keying tied to route params over manual router.subscribe cleanup when available, it's simpler and less error-prone.

Connects To

  • Ch 36: Data Loading, the loader caching/invalidation system that router.invalidate() operates on.
  • Ch 38: External Data Loading, since the same libraries recommended there for fetching typically also provide the mutation utilities discussed here.