TourKit
@tour-kit/hints

Persistence

Configure hint dismissal persistence with localStorage, sessionStorage, or custom backends to remember user choices

Hint Persistence

Why Persistence Matters

Hints are designed to guide users to features they haven't discovered yet. Once a user has seen and dismissed a hint, showing it repeatedly becomes annoying rather than helpful. Persistence ensures:

  • Dismissed hints stay dismissed - Users don't see the same hint multiple times
  • State survives page reloads - Hints remember their state across sessions
  • Cross-page consistency - Hints dismissed on one page stay dismissed everywhere

Hide vs Dismiss

The hints package has two distinct concepts for closing hints:

Hide (Temporary)

  • Closes the tooltip but doesn't mark it as dismissed
  • The hotspot remains visible
  • User can reopen the hint by clicking the hotspot
  • Default behavior when persist={false}
const { hide, isOpen } = useHint('my-hint');

// Clicking the close button hides the tooltip
// The hotspot stays visible for reopening
hide();

Dismiss (Permanent)

  • Closes the tooltip and marks it as dismissed
  • The entire hint component unmounts
  • Hint won't show again until explicitly reset
  • Enabled when persist={true}
const { dismiss, isDismissed } = useHint('my-hint');

// The hint is permanently dismissed
// Component renders null until reset
dismiss();

Using the persist Prop

The persist prop on the <Hint> component controls what happens when the tooltip close button is clicked:

// Default behavior - hint can be reopened
<Hint
  id="help-hint"
  target="#help-icon"
  content="Click for help"
  persist={false} // default
/>

// When user clicks close:
// 1. Tooltip closes
// 2. Hotspot stays visible
// 3. User can click hotspot to reopen
// Permanent dismissal
<Hint
  id="new-feature"
  target="#feature-button"
  content="Try our new feature!"
  persist={true}
/>

// When user clicks close:
// 1. Tooltip closes
// 2. Hint is marked as dismissed
// 3. Entire component unmounts
// 4. Won't show until reset

In-Memory State

By default, hint state is stored in React context (in-memory). This means:

  • State persists within the same session
  • State resets on page refresh
  • State resets when HintsProvider unmounts
function App() {
  return (
    <HintsProvider>
      {/* State lives in this provider */}
      <Hint id="hint-1" target="#a" content="..." persist />
      <Hint id="hint-2" target="#b" content="..." persist />
    </HintsProvider>
  );
}

// Refreshing the page resets all hints to their initial state

Adding Browser Storage

To persist hint state across page reloads, you need to implement a storage layer. Here's a pattern using localStorage:

Create a Storage Hook

import { useEffect, useState } from 'react';

const STORAGE_KEY = 'dismissed-hints';

function useDismissedHints() {
  const [dismissed, setDismissed] = useState<Set<string>>(() => {
    if (typeof window === 'undefined') return new Set();

    const stored = localStorage.getItem(STORAGE_KEY);
    return stored ? new Set(JSON.parse(stored)) : new Set();
  });

  const persistDismiss = (hintId: string) => {
    setDismissed(prev => {
      const next = new Set(prev);
      next.add(hintId);
      localStorage.setItem(STORAGE_KEY, JSON.stringify([...next]));
      return next;
    });
  };

  const resetDismiss = (hintId: string) => {
    setDismissed(prev => {
      const next = new Set(prev);
      next.delete(hintId);
      localStorage.setItem(STORAGE_KEY, JSON.stringify([...next]));
      return next;
    });
  };

  const resetAll = () => {
    setDismissed(new Set());
    localStorage.removeItem(STORAGE_KEY);
  };

  return { dismissed, persistDismiss, resetDismiss, resetAll };
}

Create a Persistent Hint Component

import { Hint, useHint } from '@tour-kit/hints';
import type { ComponentProps } from 'react';

interface PersistentHintProps extends ComponentProps<typeof Hint> {
  /** Uses browser storage for persistence */
  browserPersist?: boolean;
}

function PersistentHint({
  id,
  browserPersist = true,
  onDismiss,
  ...props
}: PersistentHintProps) {
  const { dismissed, persistDismiss } = useDismissedHints();

  // Don't render if already dismissed in storage
  if (browserPersist && dismissed.has(id)) {
    return null;
  }

  const handleDismiss = () => {
    if (browserPersist) {
      persistDismiss(id);
    }
    onDismiss?.();
  };

  return (
    <Hint
      id={id}
      persist={true}
      onDismiss={handleDismiss}
      {...props}
    />
  );
}

Use the Persistent Hint

function App() {
  return (
    <HintsProvider>
      {/* This hint persists across page reloads */}
      <PersistentHint
        id="new-feature"
        target="#feature-btn"
        content="Try our new feature!"
      />

      {/* This hint resets on refresh */}
      <Hint
        id="temporary-hint"
        target="#temp-btn"
        content="Just a temporary tip"
      />
    </HintsProvider>
  );
}

Storage Adapters with @tour-kit/core

For more robust storage, you can use the storage utilities from @tour-kit/core:

import {
  createStorageAdapter,
  createPrefixedStorage,
  createCookieStorage
} from '@tour-kit/core';

// localStorage (default)
const localAdapter = createStorageAdapter('localStorage');

// sessionStorage (clears on tab close)
const sessionAdapter = createStorageAdapter('sessionStorage');

// Cookies (for SSR-friendly persistence)
const cookieAdapter = createCookieStorage({ expires: 30 }); // 30 days

// With prefix to avoid collisions
const prefixedAdapter = createPrefixedStorage(localAdapter, 'myapp-hints');

Using Core Storage with Hints

import { createStorageAdapter } from '@tour-kit/core';
import { useEffect, useState } from 'react';

const storage = createStorageAdapter('localStorage');
const HINTS_KEY = 'dismissed-hints';

function usePersistentHints() {
  const [dismissed, setDismissed] = useState<string[]>([]);

  useEffect(() => {
    const stored = storage.getItem(HINTS_KEY);
    if (stored) {
      setDismissed(JSON.parse(stored));
    }
  }, []);

  const saveDismissed = (ids: string[]) => {
    storage.setItem(HINTS_KEY, JSON.stringify(ids));
    setDismissed(ids);
  };

  return { dismissed, saveDismissed };
}

Resetting Hints

Sometimes you need to reset dismissed hints (e.g., for testing or after feature updates):

Reset a Single Hint

function ResetButton({ hintId }: { hintId: string }) {
  const { reset, isDismissed } = useHint(hintId);

  if (!isDismissed) return null;

  return (
    <button onClick={reset}>
      Show "{hintId}" hint again
    </button>
  );
}

Reset All Hints

function ResetAllButton() {
  const { resetAllHints } = useHints();

  return (
    <button onClick={resetAllHints}>
      Reset All Hints
    </button>
  );
}

Admin/Debug Panel

function HintDebugPanel() {
  const { hints, resetHint, resetAllHints } = useHints();

  return (
    <div className="fixed bottom-4 right-4 p-4 bg-gray-100 rounded-lg">
      <h3 className="font-bold mb-2">Hint Debug</h3>

      <ul className="space-y-1 mb-4">
        {hints.map(hint => (
          <li key={hint.id} className="flex items-center gap-2">
            <span className={hint.isDismissed ? 'line-through' : ''}>
              {hint.id}
            </span>
            {hint.isDismissed && (
              <button
                onClick={() => resetHint(hint.id)}
                className="text-blue-500 text-sm"
              >
                Reset
              </button>
            )}
          </li>
        ))}
      </ul>

      <button
        onClick={resetAllHints}
        className="w-full bg-red-500 text-white px-3 py-1 rounded"
      >
        Reset All
      </button>
    </div>
  );
}

Best Practices

When to Use persist=

Use permanent dismissal for hints that:

  • Announce new features (show once, then never again)
  • Explain UI elements (user only needs to learn once)
  • Could become annoying if shown repeatedly
// Good: Feature announcements
<Hint id="v2-announcement" persist content="Check out our new dashboard!" />

// Good: One-time tutorials
<Hint id="how-to-export" persist content="Click here to export data" />

When to Use persist=

Use temporary hiding for hints that:

  • Provide contextual help users might need again
  • Explain complex features
  • Serve as ongoing reference
// Good: Contextual help
<Hint id="formula-help" persist={false} content="Click for formula syntax" />

// Good: Reference information
<Hint id="keyboard-shortcuts" persist={false} content="Press ? for shortcuts" />

Versioning Hints

When you update a hint's content significantly, consider changing its ID:

// v1 - Original hint
<Hint id="feature-intro-v1" content="Basic explanation" persist />

// v2 - Updated with new content
<Hint id="feature-intro-v2" content="New, better explanation" persist />

// Users who dismissed v1 will see v2

On this page