diff --git a/docs/router/api/router/useMatchRouteHook.md b/docs/router/api/router/useMatchRouteHook.md
index 01c26d07c6..e67ccb6cbd 100644
--- a/docs/router/api/router/useMatchRouteHook.md
+++ b/docs/router/api/router/useMatchRouteHook.md
@@ -3,7 +3,9 @@ id: useMatchRouteHook
title: useMatchRoute hook
---
-The `useMatchRoute` hook is a hook that returns a `matchRoute` function that can be used to match a route against either the current or pending location.
+The `useMatchRoute` hook returns a `matchRoute` function that can be used to match a route against either the current or pending location. The hook subscribes the component to changes in the router state used for matching, making it useful when the match result affects what the component renders.
+
+The `matchRoute` function's identity changes when that router state changes. For an imperative check, such as one performed in an event handler, use [`useRouter`](./useRouterHook.md) and call `router.matchRoute` instead. The router instance is stable, and this avoids subscribing the component to router state that it does not use while rendering.
## useMatchRoute returns
@@ -25,6 +27,8 @@ The `matchRoute` function accepts a single argument, an `options` object.
## Examples
+Use `useMatchRoute` when the result determines the rendered output:
+
```tsx
import { useMatchRoute } from '@tanstack/react-router'
@@ -33,7 +37,36 @@ function Component() {
const matchRoute = useMatchRoute()
const params = matchRoute({ to: '/posts/$postId' })
// ^ { postId: '123' }
+
+ return params ? :
}
+```
+
+For a check made at the time of an event, call `router.matchRoute` directly:
+
+```tsx
+import { useRouter } from '@tanstack/react-router'
+
+function Component() {
+ const router = useRouter()
+
+ return (
+
+ )
+}
+```
+
+Additional matching examples:
+
+```tsx
+import { useMatchRoute } from '@tanstack/react-router'
// Current location: /posts/123
function Component() {
diff --git a/docs/router/guide/navigation.md b/docs/router/guide/navigation.md
index b2bb03ad0e..f89c111499 100644
--- a/docs/router/guide/navigation.md
+++ b/docs/router/guide/navigation.md
@@ -792,7 +792,7 @@ The `router.navigate` method is the same as the `navigate` function returned by
## `useMatchRoute` and ``
-The `useMatchRoute` hook and `` component are the same thing, but the hook is a bit more flexible. They both accept the standard navigation `ToOptions` interface either as options or props and return `true/false` if that route is currently matched. It also has a handy `pending` option that will return `true` if the route is currently pending (e.g. a route is currently transitioning to that route). This can be extremely useful for showing optimistic UI around where a user is navigating:
+The `useMatchRoute` hook and `` component are the same thing, but the hook is a bit more flexible. They both accept the standard navigation `ToOptions` interface, either as options or props, to determine whether a route is currently matched. The `pending` option checks whether the route is currently pending (e.g. the router is currently transitioning to that route). This can be extremely useful for showing optimistic UI around where a user is navigating:
```tsx
function Component() {
@@ -828,7 +828,7 @@ function Component() {
}
```
-The hook version `useMatchRoute` returns a function that can be called programmatically to check if a route is matched:
+The hook version `useMatchRoute` returns a function for checking whether a route is matched. It subscribes the component to the router state used for matching, so use it when the result affects rendering or to trigger an effect. This subscription ensures the component updates as the current or pending location changes:
```tsx
function Component() {
@@ -838,7 +838,7 @@ function Component() {
if (matchRoute({ to: '/users', pending: true })) {
console.info('The /users route is matched and pending')
}
- })
+ }, [matchRoute])
return (
@@ -848,6 +848,26 @@ function Component() {
}
```
+The `matchRoute` function returned by `useMatchRoute` changes identity when the relevant router state changes. If you only need to check the route at the time an event occurs, use the stable router instance returned by `useRouter` and call `router.matchRoute` directly. This reads the latest router state without subscribing the component to matching state that it does not use while rendering:
+
+```tsx
+function Component() {
+ const router = useRouter()
+
+ return (
+
+ )
+}
+```
+
---
Phew! That's a lot of navigating! That said, hopefully you're feeling pretty good about getting around your application now. Let's move on!
diff --git a/e2e/react-router/react-compiler/index.html b/e2e/react-router/react-compiler/index.html
new file mode 100644
index 0000000000..0c5b6b471c
--- /dev/null
+++ b/e2e/react-router/react-compiler/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ React Compiler useMatchRoute test
+
+
+
+
+
+
diff --git a/e2e/react-router/react-compiler/package.json b/e2e/react-router/react-compiler/package.json
new file mode 100644
index 0000000000..d4d0e6473a
--- /dev/null
+++ b/e2e/react-router/react-compiler/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "tanstack-router-e2e-react-compiler",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --port 3000",
+ "dev:e2e": "vite",
+ "build": "vite build && tsc --noEmit",
+ "preview": "vite preview",
+ "start": "vite",
+ "test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
+ },
+ "dependencies": {
+ "@tanstack/react-router": "workspace:^",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.29.0",
+ "@playwright/test": "^1.61.0",
+ "@rolldown/plugin-babel": "^0.2.0",
+ "@tanstack/router-e2e-utils": "workspace:^",
+ "@types/react": "^19.0.8",
+ "@types/react-dom": "^19.0.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.14"
+ }
+}
diff --git a/e2e/react-router/react-compiler/playwright.config.ts b/e2e/react-router/react-compiler/playwright.config.ts
new file mode 100644
index 0000000000..61a995a815
--- /dev/null
+++ b/e2e/react-router/react-compiler/playwright.config.ts
@@ -0,0 +1,25 @@
+import { defineConfig, devices } from '@playwright/test'
+import { getTestServerPort } from '@tanstack/router-e2e-utils'
+import packageJson from './package.json' with { type: 'json' }
+
+const PORT = await getTestServerPort(packageJson.name)
+const baseURL = `http://localhost:${PORT}`
+
+export default defineConfig({
+ testDir: './tests',
+ workers: 1,
+ reporter: [['line']],
+ use: { baseURL },
+ webServer: {
+ command: `VITE_NODE_ENV="test" VITE_SERVER_PORT=${PORT} pnpm dev:e2e --port ${PORT}`,
+ url: baseURL,
+ reuseExistingServer: !process.env.CI,
+ stdout: 'pipe',
+ },
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+})
diff --git a/e2e/react-router/react-compiler/src/main.tsx b/e2e/react-router/react-compiler/src/main.tsx
new file mode 100644
index 0000000000..e6e549da83
--- /dev/null
+++ b/e2e/react-router/react-compiler/src/main.tsx
@@ -0,0 +1,73 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import {
+ Link,
+ Outlet,
+ RouterProvider,
+ createRootRoute,
+ createRoute,
+ createRouter,
+ linkOptions,
+ useMatchRoute,
+} from '@tanstack/react-router'
+
+const links = linkOptions([
+ { to: '/home', label: 'Home' },
+ { to: '/about', label: 'About' },
+])
+
+function useRouteName() {
+ const matchRoute = useMatchRoute()
+
+ return links.find((link) => matchRoute(link))?.label ?? 'Unknown'
+}
+
+function RootComponent() {
+ const matchedRoute = useRouteName()
+
+ return (
+ <>
+
+