React Router Adapter
React Router v6/v7 adapter: enable cross-route tours with useLocation and useNavigate integration in SPAs
Router adapter for React Router v6 (react-router-dom) and v7 (react-router).
When to Use
Use this adapter when:
- You're building a React SPA with Vite, Create React App, or similar
- Your app uses React Router for client-side routing
- You need multi-page tours that navigate between routes
Installation
The adapter is included in @tour-kit/react. Install React Router:
pnpm add @tour-kit/react react-routerpnpm add @tour-kit/react react-router-domUsage
import { BrowserRouter } from 'react-router-dom'; // or 'react-router'
import { MultiTourKitProvider, useReactRouter } from '@tour-kit/react';
function TourProviderWithRouter({ children }) {
const router = useReactRouter();
return (
<MultiTourKitProvider
router={router}
routePersistence={{ enabled: true }}
>
{children}
</MultiTourKitProvider>
);
}
function App() {
return (
<BrowserRouter>
<TourProviderWithRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</TourProviderWithRouter>
</BrowserRouter>
);
}import { useLocation, useNavigate } from 'react-router-dom';
import { MultiTourKitProvider, createReactRouterAdapter } from '@tour-kit/react';
const useRouter = createReactRouterAdapter(useLocation, useNavigate);
function TourProviderWithRouter({ children }) {
const router = useRouter();
return (
<MultiTourKitProvider router={router}>
{children}
</MultiTourKitProvider>
);
}The adapter must be used inside BrowserRouter (or any Router) since it depends on React Router's context.
useReactRouter
Direct hook that automatically imports from react-router (v7) or react-router-dom (v6).
import { useReactRouter } from '@tour-kit/react';
const router = useReactRouter();Return Value
Returns a RouterAdapter with these methods:
Prop
Type
createReactRouterAdapter
Factory function for custom setups.
import { createReactRouterAdapter } from '@tour-kit/react';
import { useLocation, useNavigate } from 'react-router-dom';
const useRouter = createReactRouterAdapter(useLocation, useNavigate);Parameters
Prop
Type
Complete Example
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import {
MultiTourKitProvider,
useReactRouter,
Tour,
TourStep,
TourOverlay,
TourCard,
useTour,
} from '@tour-kit/react';
import { Home, Dashboard, Settings } from './pages';
function TourProviderWithRouter({ children }) {
const router = useReactRouter();
return (
<MultiTourKitProvider
router={router}
routePersistence={{
enabled: true,
storage: 'sessionStorage', // Clears on tab close
}}
autoNavigate={true}
>
<Tour id="onboarding">
<TourStep
target="#hero"
title="Welcome!"
content="Let's show you around our app."
route="/"
routeMatch="exact"
/>
<TourStep
target="#dashboard-chart"
title="Your Dashboard"
content="View your analytics here."
route="/dashboard"
/>
<TourStep
target="#settings-theme"
title="Settings"
content="Customize your experience."
route="/settings"
/>
</Tour>
<TourOverlay />
<TourCard />
{children}
<TourButton />
</MultiTourKitProvider>
);
}
function TourButton() {
const { start, isActive } = useTour();
if (isActive) return null;
return (
<button
onClick={() => start('onboarding')}
className="fixed bottom-4 right-4 btn-primary"
>
Start Tour
</button>
);
}
export default function App() {
return (
<BrowserRouter>
<TourProviderWithRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</TourProviderWithRouter>
</BrowserRouter>
);
}React Router v6 vs v7
The adapter works with both versions:
| Feature | v6 (react-router-dom) | v7 (react-router) |
|---|---|---|
| Package | react-router-dom | react-router |
| Import | Auto-detected | Auto-detected |
| API | Same | Same |
| Detection | Falls back if v7 not found | Tried first |
// Adapter tries in order:
try {
require('react-router'); // v7
} catch {
require('react-router-dom'); // v6 fallback
}Route Change Detection
The adapter uses location.pathname reactivity:
// Internal behavior:
useEffect(() => {
if (previousPath !== location.pathname) {
for (const callback of callbacks) {
callback(location.pathname);
}
}
}, [location.pathname]);The onRouteChange callback fires immediately with the current route when subscribed, then on each subsequent navigation.
Nested Routes
For nested routes, use startsWith matching:
// Routes setup
<Route path="/dashboard/*" element={<Dashboard />}>
<Route path="analytics" element={<Analytics />} />
<Route path="settings" element={<Settings />} />
</Route>
// Tour step that works for all dashboard routes
<TourStep
target="#dashboard-nav"
title="Dashboard"
route="/dashboard"
routeMatch="startsWith" // Matches /dashboard, /dashboard/analytics, etc.
/>Hash Router Support
For HashRouter, the adapter still works since useLocation() returns the same shape:
import { HashRouter } from 'react-router-dom';
function App() {
return (
<HashRouter>
<TourProviderWithRouter>
<Routes />
</TourProviderWithRouter>
</HashRouter>
);
}Troubleshooting
"Neither react-router nor react-router-dom found"
Install one of them:
pnpm add react-router-dom # v6
# or
pnpm add react-router # v7"useLocation must be used within a Router"
Ensure the adapter is used inside a Router:
// Wrong - adapter outside router
<TourProviderWithRouter>
<BrowserRouter>
<Routes />
</BrowserRouter>
</TourProviderWithRouter>
// Correct - adapter inside router
<BrowserRouter>
<TourProviderWithRouter>
<Routes />
</TourProviderWithRouter>
</BrowserRouter>Routes Not Matching
Check the current pathname:
const { getCurrentRoute } = useReactRouter();
console.log(getCurrentRoute()); // Check actual pathnameRelated
- Vite Integration Guide - Complete Vite + React Router setup
- Router Adapters Overview - All available adapters
- MultiTourKitProvider - Provider configuration