Skip to main content

React Hooks

Type-safe React hooks for accessing router state, route data, and navigation.

useRouter

Access the router instance.
import { useRouter } from '@tanstack/react-router'

function MyComponent() {
  const router = useRouter()
  
  const handleInvalidate = () => {
    router.invalidate()
  }
  
  return <button onClick={handleInvalidate}>Refresh Data</button>
}

Router Methods

const router = useRouter()

// Navigate
await router.navigate({ to: '/posts' })

// Invalidate and refetch
await router.invalidate()

// Build location
const location = router.buildLocation({ to: '/posts' })

// Match route
const match = router.matchRoute({ to: '/posts' })

// Preload route
await router.preloadRoute({ to: '/posts/$postId', params: { postId: '123' } })

useRouterState

Access and subscribe to router state.
import { useRouterState } from '@tanstack/react-router'

function NavigationStatus() {
  const isLoading = useRouterState({ select: (s) => s.isLoading })
  const location = useRouterState({ select: (s) => s.location })
  
  return (
    <div>
      {isLoading && <LoadingBar />}
      <p>Current path: {location.pathname}</p>
    </div>
  )
}

Options

select
(state: RouterState) => any
Select specific data from router state.
const pathname = useRouterState({
  select: (s) => s.location.pathname
})

RouterState Properties

location
ParsedLocation
Current location object.
matches
RouteMatch[]
Array of current route matches.
pendingMatches
RouteMatch[]
Matches being loaded.
status
'pending' | 'idle'
Router status.
isLoading
boolean
Whether router is loading.
isTransitioning
boolean
Whether router is transitioning.

useLocation

Access the current location.
import { useLocation } from '@tanstack/react-router'

function LocationInfo() {
  const location = useLocation()
  
  return (
    <div>
      <p>Pathname: {location.pathname}</p>
      <p>Search: {JSON.stringify(location.search)}</p>
      <p>Hash: {location.hash}</p>
    </div>
  )
}

Options

select
(location: ParsedLocation) => any
Select specific data from location.
const pathname = useLocation({
  select: (location) => location.pathname
})

useParams

Access route parameters with type safety.
import { useParams } from '@tanstack/react-router'

function PostDetails() {
  const params = useParams({ from: '/posts/$postId' })
  //    ^? { postId: string }
  
  return <div>Post ID: {params.postId}</div>
}

Options

from
string
Route ID for type inference.
const params = useParams({ from: '/posts/$postId' })
strict
boolean
Throw error if route doesn’t match.Default: true
select
(params) => any
Select specific params.
const postId = useParams({
  from: '/posts/$postId',
  select: (params) => params.postId
})

useSearch

Access validated search parameters.
import { useSearch } from '@tanstack/react-router'
import { z } from 'zod'

// Route with validated search
const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  validateSearch: z.object({
    page: z.number().default(1),
    filter: z.string().optional()
  })
})

function PostsList() {
  const { page, filter } = useSearch({ from: '/posts' })
  //    ^? { page: number, filter?: string }
  
  return <div>Page {page}</div>
}

Options

from
string
Route ID for type inference.
strict
boolean
Throw error if route doesn’t match.Default: true
select
(search) => any
Select specific search params.
const page = useSearch({
  from: '/posts',
  select: (search) => search.page
})

useLoaderData

Access loader data with type safety.
import { useLoaderData } from '@tanstack/react-router'

const postRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts/$postId',
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId)
    return { post }
  }
})

function PostDetails() {
  const { post } = useLoaderData({ from: '/posts/$postId' })
  //    ^? { post: Post }
  
  return <h1>{post.title}</h1>
}

Options

from
string
Route ID for type inference.
strict
boolean
Throw error if route doesn’t match.Default: true
select
(data) => any
Select specific data.
const title = useLoaderData({
  from: '/posts/$postId',
  select: (data) => data.post.title
})

useLoaderDeps

Access loader dependencies.
import { useLoaderDeps } from '@tanstack/react-router'

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  loaderDeps: ({ search }) => ({
    page: search.page,
    filter: search.filter
  }),
  loader: async ({ deps }) => {
    const posts = await fetchPosts(deps.page, deps.filter)
    return { posts }
  }
})

function PostsList() {
  const deps = useLoaderDeps({ from: '/posts' })
  return <div>Loading page {deps.page}</div>
}

useRouteContext

Access route context.
import { useRouteContext } from '@tanstack/react-router'

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  context: () => ({
    title: 'Blog Posts'
  })
})

function PostsHeader() {
  const context = useRouteContext({ from: '/posts' })
  return <h1>{context.title}</h1>
}

Options

from
string
Route ID for type inference.
strict
boolean
Throw error if route doesn’t match.Default: true
select
(context) => any
Select specific context data.

useMatch

Access a specific route match.
import { useMatch } from '@tanstack/react-router'

function PostStatus() {
  const match = useMatch({ from: '/posts/$postId' })
  
  return (
    <div>
      <p>Status: {match.status}</p>
      <p>Loading: {match.isFetching ? 'Yes' : 'No'}</p>
    </div>
  )
}

Options

from
string
Route ID for type inference.
strict
boolean
Throw error if route doesn’t match.Default: true
select
(match) => any
Select specific match data.
const status = useMatch({
  from: '/posts/$postId',
  select: (match) => match.status
})

useMatches

Access all current route matches.
import { useMatches } from '@tanstack/react-router'

function Breadcrumbs() {
  const matches = useMatches()
  
  return (
    <nav>
      {matches.map((match, i) => (
        <span key={match.id}>
          {i > 0 && ' > '}
          {match.staticData?.title || match.routeId}
        </span>
      ))}
    </nav>
  )
}

Options

select
(matches: RouteMatch[]) => any
Select specific data from matches.
const titles = useMatches({
  select: (matches) => matches.map(m => m.staticData?.title)
})

useParentMatches

Access parent route matches.
import { useParentMatches } from '@tanstack/react-router'

function ChildRoute() {
  const parentMatches = useParentMatches()
  
  return (
    <div>
      Parent routes: {parentMatches.map(m => m.routeId).join(' > ')}
    </div>
  )
}

useChildMatches

Access child route matches.
import { useChildMatches } from '@tanstack/react-router'

function ParentRoute() {
  const childMatches = useChildMatches()
  
  return (
    <div>
      Has children: {childMatches.length > 0 ? 'Yes' : 'No'}
    </div>
  )
}

useMatchRoute

Check if a route matches.
import { useMatchRoute } from '@tanstack/react-router'

function ConditionalNav() {
  const matchRoute = useMatchRoute()
  
  const isPostsPage = matchRoute({ to: '/posts' })
  const isAdminPage = matchRoute({ to: '/admin', fuzzy: true })
  
  return (
    <nav>
      {isPostsPage && <PostsNav />}
      {isAdminPage && <AdminNav />}
    </nav>
  )
}

useBlocker

Block navigation with confirmation.
import { useBlocker } from '@tanstack/react-router'

function EditForm() {
  const [isDirty, setIsDirty] = useState(false)
  
  useBlocker({
    blockerFn: async () => {
      if (!isDirty) return true
      
      return window.confirm(
        'You have unsaved changes. Are you sure you want to leave?'
      )
    },
    enableBeforeUnload: isDirty
  })
  
  return <form onChange={() => setIsDirty(true)}>...</form>
}

Options

blockerFn
BlockerFn
required
Function to determine if navigation should be blocked.
blockerFn: async ({ currentLocation, nextLocation, action }) => {
  return window.confirm('Leave page?')
}
enableBeforeUnload
boolean | (() => boolean)
Enable browser beforeunload warning.
enableBeforeUnload: isDirty
enableBeforeUnload: () => form.hasChanges()

useCanGoBack

Check if the router can navigate back.
import { useCanGoBack, useRouter } from '@tanstack/react-router'

function BackButton() {
  const router = useRouter()
  const canGoBack = useCanGoBack()
  
  if (!canGoBack) {
    return null
  }
  
  return (
    <button onClick={() => router.history.back()}>
      Back
    </button>
  )
}

useAwaited

Await deferred promises in components.
import { useAwaited, Await } from '@tanstack/react-router'
import { defer } from '@tanstack/react-router'

const postRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts/$postId',
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId)
    const relatedPosts = defer(fetchRelatedPosts(params.postId))
    
    return { post, relatedPosts }
  }
})

function PostDetails() {
  const { post, relatedPosts } = postRoute.useLoaderData()
  
  return (
    <div>
      <h1>{post.title}</h1>
      
      <Suspense fallback={<div>Loading related...</div>}>
        <Await promise={relatedPosts}>
          {(related) => (
            <RelatedPosts posts={related} />
          )}
        </Await>
      </Suspense>
    </div>
  )
}

Examples

Global Loading State

import { useRouterState } from '@tanstack/react-router'

function GlobalLoadingBar() {
  const isLoading = useRouterState({ 
    select: (s) => s.isLoading 
  })
  
  if (!isLoading) return null
  
  return (
    <div className="fixed top-0 left-0 right-0 h-1 bg-blue-600 animate-pulse" />
  )
}

Authentication Check

import { useRouteContext, Navigate } from '@tanstack/react-router'

function ProtectedRoute({ children }) {
  const { user } = useRouteContext({ from: '__root__' })
  
  if (!user) {
    return <Navigate to="/login" replace />
  }
  
  return children
}

Dynamic Title

import { useMatches } from '@tanstack/react-router'
import { useEffect } from 'react'

function DynamicTitle() {
  const matches = useMatches()
  
  useEffect(() => {
    const titles = matches
      .map(m => m.staticData?.title)
      .filter(Boolean)
    
    document.title = titles.reverse().join(' | ')
  }, [matches])
  
  return null
}

Form with Unsaved Changes

import { useBlocker } from '@tanstack/react-router'
import { useState } from 'react'

function EditForm({ initialData }) {
  const [data, setData] = useState(initialData)
  const isDirty = JSON.stringify(data) !== JSON.stringify(initialData)
  
  useBlocker({
    blockerFn: async () => {
      if (!isDirty) return true
      return window.confirm('Discard changes?')
    },
    enableBeforeUnload: isDirty
  })
  
  return (
    <form>
      <input 
        value={data.title}
        onChange={(e) => setData({ ...data, title: e.target.value })}
      />
      {isDirty && <span className="text-orange-500">Unsaved changes</span>}
    </form>
  )
}

Build docs developers (and LLMs) love