Skip to main content
userTourKit
@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 closes
const persistence = useRoutePersistence({
  enabled: true,
  storage: 'memory',
});

// State persists across:
// - SPA navigations only
// State is cleared when:
// - Page refreshes
// - Browser restarts

Multi-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 === 2

Cross-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 true

SSR 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 normally

Error 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 warning