@tour-kit/coreHooks
useRoutePersistence
useRoutePersistence hook: maintain tour progress when users navigate between pages in multi-page applications
domidex01Published
Persist tour state across page navigations. Essential for multi-page tours where users navigate between routes while completing a tour.
Why Use This Hook?
When a tour spans multiple pages, the state (current step, completed tours) needs to survive page navigations. This hook:
- Persists across navigations - State survives full page reloads
- Supports multiple storage backends - localStorage, sessionStorage, or memory
- Handles expiration - Automatically clears stale state
- Syncs across tabs - Optional cross-tab synchronization
- SSR-safe - Works in server-side rendering environments
Usage
import { useRoutePersistence } from '@tour-kit/core';
function MultiPageTour() {
const persistence = useRoutePersistence({
enabled: true,
storage: 'localStorage',
key: 'my-tour-state',
expiryMs: 24 * 60 * 60 * 1000, // 24 hours
syncTabs: true,
});
// Save state when tour progresses
const handleStepChange = (state) => {
persistence.save({
tourId: 'onboarding',
currentStepIndex: state.stepIndex,
completedTours: state.completedTours,
});
};
// Load state on mount
useEffect(() => {
const savedState = persistence.load();
if (savedState && !persistence.isStale()) {
// Resume tour from saved state
startTour(savedState.tourId, savedState.stepIndex);
}
}, []);
return <Tour onStepChange={handleStepChange}>{/* ... */}</Tour>;
}Configuration
Prop
Type
Return Value
Prop
Type
Persisted State Shape
The load() method returns:
interface PersistedRouteState {
tourId: string | null;
stepIndex: number;
completedTours: string[];
skippedTours: string[];
timestamp: number;
}Storage Options
const persistence = useRoutePersistence({
enabled: true,
storage: 'localStorage',
syncTabs: true, // Works with localStorage
});
// State persists across:
// - Page refreshes
// - Browser restarts
// - Multiple tabs (with syncTabs)const persistence = useRoutePersistence({
enabled: true,
storage: 'sessionStorage',
});
// State persists across:
// - Page navigations within the session
// - Page refreshes
// State is cleared when:
// - Browser tab closesconst persistence = useRoutePersistence({
enabled: true,
storage: 'memory',
});
// State persists across:
// - SPA navigations only
// State is cleared when:
// - Page refreshes
// - Browser restartsMulti-Page Tour Example
import { useRoutePersistence } from '@tour-kit/core';
import { useNavigate, useLocation } from 'react-router-dom';
function OnboardingTour() {
const navigate = useNavigate();
const location = useLocation();
const persistence = useRoutePersistence({
enabled: true,
storage: 'localStorage',
expiryMs: 7 * 24 * 60 * 60 * 1000, // 7 days
});
// Resume tour on mount
useEffect(() => {
const state = persistence.load();
if (state?.tourId === 'onboarding' && !persistence.isStale()) {
// Navigate to the correct page for the saved step
const stepRoutes = ['/', '/dashboard', '/settings'];
const targetRoute = stepRoutes[state.stepIndex];
if (location.pathname !== targetRoute) {
navigate(targetRoute);
}
}
}, []);
// Save state when step changes
const handleStepChange = (stepIndex: number) => {
persistence.save({
tourId: 'onboarding',
currentStepIndex: stepIndex,
completedTours: [],
skippedTours: [],
});
};
// Clear state on complete
const handleComplete = () => {
persistence.clear();
};
return (
<Tour
id="onboarding"
onStepChange={handleStepChange}
onComplete={handleComplete}
>
{/* Steps */}
</Tour>
);
}Cross-Tab Synchronization
When syncTabs is enabled, state changes propagate to other tabs:
const persistence = useRoutePersistence({
enabled: true,
storage: 'localStorage',
syncTabs: true,
});
// Tab 1: User completes step
persistence.save({ tourId: 'demo', currentStepIndex: 2 });
// Tab 2: Can detect the change
const state = persistence.load();
// state.stepIndex === 2Cross-tab sync only works with localStorage. Session storage is tab-specific by design.
Expiration Handling
State automatically expires after the configured time:
const persistence = useRoutePersistence({
enabled: true,
expiryMs: 60 * 60 * 1000, // 1 hour
});
// After 1 hour:
const state = persistence.load(); // Returns null
persistence.isStale(); // Returns trueSSR Considerations
The hook handles SSR gracefully:
// During SSR:
// - Uses in-memory storage
// - load() returns null
// - save() is a no-op
// After hydration:
// - Switches to configured storage
// - State persists normallyError Handling
Storage operations are wrapped in try-catch:
// If storage is unavailable (e.g., private browsing):
persistence.save({ tourId: 'demo' }); // Silent failure, logs warning
persistence.load(); // Returns null
// If JSON parsing fails:
persistence.load(); // Returns null, logs warningRelated
- usePersistence - General-purpose persistence hook
- Next.js Integration - Multi-page tours with Next.js
- Router Integration - Deep dive into multi-page tours