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
Array of current route matches.
Whether router is loading.
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
Route ID for type inference.const params = useParams({ from: '/posts/$postId' })
Throw error if route doesn’t match.Default: true
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
Route ID for type inference.
Throw error if route doesn’t match.Default: true
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
Route ID for type inference.
Throw error if route doesn’t match.Default: true
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
Route ID for type inference.
Throw error if route doesn’t match.Default: true
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
Route ID for type inference.
Throw error if route doesn’t match.Default: true
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
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
}
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>
)
}