or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

async-operations.mddata-structures.mddom-interactions.mdeffects.mdindex.mdperformance.mdspecialized-hooks.mdstate-management.mdstorage.mdtimers.md
tile.json

storage.mddocs/

Storage

Browser storage integration hooks that provide automatic serialization, state synchronization, and seamless integration with localStorage, sessionStorage, and cookies.

Capabilities

Local Storage

useLocalStorageState

Synchronizes state with localStorage, providing automatic serialization and cross-tab synchronization.

/**
 * Synchronizes state with localStorage
 * @param key - Storage key for localStorage
 * @param options - Configuration options
 * @returns Array with current state and setter function
 */
function useLocalStorageState<T>(
  key: string,
  options?: Options<T>
): [T | undefined, (value?: T | ((prev?: T) => T)) => void];

interface Options<T> {
  /** Default value if no stored value exists */
  defaultValue?: T | (() => T);
  /** Custom serializer for complex data types */
  serializer?: Serializer<T>;
  /** Error handler for storage operations */
  onError?: (error: Error) => void;
}

interface Serializer<T> {
  /** Deserialize stored string to value */
  read(value: string): T;
  /** Serialize value to string for storage */
  write(value: T): string;
}

Basic Usage Example:

import { useLocalStorageState } from 'ahooks';

interface UserPreferences {
  theme: 'light' | 'dark';
  language: string;
  notifications: boolean;
}

function UserSettings() {
  // Simple string storage
  const [username, setUsername] = useLocalStorageState('username', {
    defaultValue: 'Anonymous'
  });
  
  // Object storage with automatic JSON serialization
  const [preferences, setPreferences] = useLocalStorageState<UserPreferences>('user-prefs', {
    defaultValue: {
      theme: 'light',
      language: 'en',
      notifications: true
    }
  });
  
  // Array storage
  const [recentSearches, setRecentSearches] = useLocalStorageState<string[]>('recent-searches', {
    defaultValue: []
  });
  
  const addSearch = (term: string) => {
    setRecentSearches(prev => [term, ...(prev || [])].slice(0, 5));
  };
  
  const updateTheme = (theme: 'light' | 'dark') => {
    setPreferences(prev => prev ? { ...prev, theme } : { theme, language: 'en', notifications: true });
  };
  
  return (
    <div className={`theme-${preferences?.theme}`}>
      <h2>User Settings</h2>
      
      <div>
        <label>Username:</label>
        <input 
          value={username || ''} 
          onChange={(e) => setUsername(e.target.value)} 
        />
      </div>
      
      <div>
        <label>Theme:</label>
        <select 
          value={preferences?.theme} 
          onChange={(e) => updateTheme(e.target.value as 'light' | 'dark')}
        >
          <option value="light">Light</option>
          <option value="dark">Dark</option>
        </select>
      </div>
      
      <div>
        <h3>Recent Searches:</h3>
        {recentSearches?.map((term, index) => (
          <div key={index}>{term}</div>
        ))}
        <button onClick={() => addSearch(`Search ${Date.now()}`)}>
          Add Random Search
        </button>
      </div>
    </div>
  );
}

Custom Serializer Example:

import { useLocalStorageState } from 'ahooks';

function CustomSerializerExample() {
  // Custom serializer for Date objects
  const [lastVisit, setLastVisit] = useLocalStorageState<Date>('last-visit', {
    defaultValue: new Date(),
    serializer: {
      read: (value: string) => new Date(value),
      write: (value: Date) => value.toISOString()
    },
    onError: (error) => {
      console.error('Storage error:', error);
    }
  });
  
  // Custom serializer for Map objects
  const [dataMap, setDataMap] = useLocalStorageState<Map<string, number>>('data-map', {
    defaultValue: new Map([['initial', 1]]),
    serializer: {
      read: (value: string) => new Map(JSON.parse(value)),
      write: (value: Map<string, number>) => JSON.stringify(Array.from(value.entries()))
    }
  });
  
  return (
    <div>
      <p>Last visit: {lastVisit?.toLocaleString()}</p>
      <button onClick={() => setLastVisit(new Date())}>
        Update Last Visit
      </button>
      
      <p>Map size: {dataMap?.size}</p>
      <button onClick={() => setDataMap(prev => {
        const newMap = new Map(prev);
        newMap.set(`key-${Date.now()}`, Math.random());
        return newMap;
      })}>
        Add to Map
      </button>
    </div>
  );
}

Session Storage

useSessionStorageState

Synchronizes state with sessionStorage (data persists only for the session/tab).

/**
 * Synchronizes state with sessionStorage
 * @param key - Storage key for sessionStorage
 * @param options - Configuration options (same as useLocalStorageState)
 * @returns Array with current state and setter function
 */
function useSessionStorageState<T>(
  key: string,
  options?: Options<T>
): [T | undefined, (value?: T | ((prev?: T) => T)) => void];

Usage Example:

import { useSessionStorageState } from 'ahooks';

function SessionDataExample() {
  // Form data that should only persist during the session
  const [formData, setFormData] = useSessionStorageState('temp-form', {
    defaultValue: { name: '', email: '', message: '' }
  });
  
  // Shopping cart that clears when browser/tab closes
  const [cart, setCart] = useSessionStorageState<CartItem[]>('session-cart', {
    defaultValue: []
  });
  
  // Navigation state
  const [currentStep, setCurrentStep] = useSessionStorageState('wizard-step', {
    defaultValue: 1
  });
  
  const updateForm = (field: string, value: string) => {
    setFormData(prev => prev ? { ...prev, [field]: value } : { name: '', email: '', message: '', [field]: value });
  };
  
  return (
    <div>
      <h2>Session Data (clears when tab closes)</h2>
      
      <div>
        <h3>Step {currentStep} of 3</h3>
        <button onClick={() => setCurrentStep(Math.max(1, (currentStep || 1) - 1))}>
          Previous
        </button>
        <button onClick={() => setCurrentStep(Math.min(3, (currentStep || 1) + 1))}>
          Next
        </button>
      </div>
      
      <form>
        <input
          placeholder="Name"
          value={formData?.name || ''}
          onChange={(e) => updateForm('name', e.target.value)}
        />
        <input
          placeholder="Email"
          value={formData?.email || ''}
          onChange={(e) => updateForm('email', e.target.value)}
        />
        <textarea
          placeholder="Message"
          value={formData?.message || ''}
          onChange={(e) => updateForm('message', e.target.value)}
        />
      </form>
      
      <p>Cart items: {cart?.length || 0}</p>
      <button onClick={() => setCart(prev => [...(prev || []), { id: Date.now(), name: `Item ${Date.now()}` }])}>
        Add to Cart
      </button>
    </div>
  );
}

interface CartItem {
  id: number;
  name: string;
}

Cookie Storage

useCookieState

Synchronizes state with browser cookies, supporting all cookie attributes and configurations.

/**
 * Synchronizes state with browser cookies
 * @param cookieKey - Cookie name
 * @param options - Cookie configuration options
 * @returns Array with current state and updater function
 */
function useCookieState(cookieKey: string, options?: Options): [State, UpdateState];

type State = string | undefined;

interface Options extends Cookies.CookieAttributes {
  /** Default value if cookie doesn't exist */
  defaultValue?: State | (() => State);
}

type UpdateState = (
  newValue: State | ((prevState: State) => State),
  newOptions?: Cookies.CookieAttributes
) => void;

// Cookie attributes (from js-cookie library)
namespace Cookies {
  interface CookieAttributes {
    /** Expiration date/time */
    expires?: number | Date;
    /** Cookie path (default: '/') */
    path?: string;
    /** Cookie domain */
    domain?: string;
    /** Secure cookie (HTTPS only) */
    secure?: boolean;
    /** SameSite policy */
    sameSite?: 'strict' | 'lax' | 'none';
    /** Max age in seconds */
    maxAge?: number;
    /** HttpOnly flag (server-side only) */
    httpOnly?: boolean;
  }
}

Usage Example:

import { useCookieState } from 'ahooks';

function CookieExample() {
  // Basic cookie usage
  const [userToken, setUserToken] = useCookieState('auth-token');
  
  // Cookie with expiration (7 days)
  const [rememberMe, setRememberMe] = useCookieState('remember-me', {
    defaultValue: 'false',
    expires: 7 // 7 days
  });
  
  // Cookie with custom options
  const [sessionId, setSessionId] = useCookieState('session-id', {
    defaultValue: '',
    path: '/',
    secure: true,
    sameSite: 'lax',
    maxAge: 30 * 60 // 30 minutes in seconds
  });
  
  // Preference cookie with longer expiration
  const [language, setLanguage] = useCookieState('preferred-language', {
    defaultValue: 'en',
    expires: 365, // 1 year
    domain: '.mysite.com' // Available across subdomains
  });
  
  const login = () => {
    const token = 'jwt-token-' + Date.now();
    
    // Set token with custom expiration based on remember me
    setUserToken(token, {
      expires: rememberMe === 'true' ? 30 : 1, // 30 days if remembered, 1 day otherwise
      secure: window.location.protocol === 'https:',
      sameSite: 'lax'
    });
    
    setSessionId('session-' + Date.now());
  };
  
  const logout = () => {
    setUserToken(undefined); // Remove cookie
    setSessionId(undefined);
  };
  
  return (
    <div>
      <h2>Cookie Management</h2>
      
      <div>
        <p>Logged in: {userToken ? 'Yes' : 'No'}</p>
        <p>Session ID: {sessionId || 'None'}</p>
        <p>Language: {language}</p>
      </div>
      
      <div>
        <label>
          <input
            type="checkbox"
            checked={rememberMe === 'true'}
            onChange={(e) => setRememberMe(e.target.checked ? 'true' : 'false')}
          />
          Remember me
        </label>
      </div>
      
      <div>
        <select 
          value={language} 
          onChange={(e) => setLanguage(e.target.value)}
        >
          <option value="en">English</option>
          <option value="es">Español</option>
          <option value="fr">Français</option>
        </select>
      </div>
      
      <div>
        {userToken ? (
          <button onClick={logout}>Logout</button>
        ) : (
          <button onClick={login}>Login</button>
        )}
      </div>
    </div>
  );
}

Advanced Cookie Usage:

import { useCookieState } from 'ahooks';

function AdvancedCookieExample() {
  // Shopping cart with detailed cookie configuration
  const [cartCount, setCartCount] = useCookieState('cart-count', {
    defaultValue: '0',
    expires: 1, // 1 day
    path: '/shop',
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax'
  });
  
  // User preferences with function-based default
  const [theme, setTheme] = useCookieState('theme-preference', {
    defaultValue: () => {
      // Detect system preference
      return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    },
    expires: 365,
    path: '/'
  });
  
  // GDPR consent with specific configuration
  const [gdprConsent, setGdprConsent] = useCookieState('gdpr-consent', {
    expires: 365,
    path: '/',
    sameSite: 'strict'
  });
  
  const addToCart = () => {
    const currentCount = parseInt(cartCount || '0');
    setCartCount(String(currentCount + 1), {
      // Update expiration when interacting
      expires: 7 // Extend to 7 days on activity
    });
  };
  
  const acceptGdpr = () => {
    setGdprConsent('accepted', {
      expires: 365,
      secure: window.location.protocol === 'https:'
    });
  };
  
  return (
    <div className={`theme-${theme}`}>
      <h2>Advanced Cookie Usage</h2>
      
      {!gdprConsent && (
        <div className="gdpr-banner">
          <p>We use cookies to enhance your experience.</p>
          <button onClick={acceptGdpr}>Accept</button>
        </div>
      )}
      
      <div>
        <p>Cart items: {cartCount}</p>
        <button onClick={addToCart}>Add to Cart</button>
      </div>
      
      <div>
        <p>Theme: {theme}</p>
        <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
          Toggle Theme
        </button>
      </div>
    </div>
  );
}

Cross-Tab Synchronization

All storage hooks automatically synchronize state changes across browser tabs:

import { useLocalStorageState } from 'ahooks';

function SyncExample() {
  const [counter, setCounter] = useLocalStorageState('sync-counter', {
    defaultValue: 0
  });
  
  // Changes made in this tab will automatically update in other tabs
  // Changes made in other tabs will automatically update this component
  
  return (
    <div>
      <p>Counter: {counter} (synced across tabs)</p>
      <button onClick={() => setCounter((counter || 0) + 1)}>
        Increment
      </button>
      <button onClick={() => setCounter(0)}>
        Reset
      </button>
      <p>Open this page in multiple tabs to see synchronization!</p>
    </div>
  );
}

Error Handling

import { useLocalStorageState } from 'ahooks';

function ErrorHandlingExample() {
  const [data, setData] = useLocalStorageState('error-prone-data', {
    defaultValue: { complex: 'object' },
    onError: (error) => {
      console.error('Storage operation failed:', error);
      
      // Handle specific error types
      if (error.name === 'QuotaExceededError') {
        alert('Storage quota exceeded. Please clear some data.');
      } else if (error.name === 'SecurityError') {
        alert('Storage access denied. Check privacy settings.');
      }
      
      // Log to error reporting service
      // reportError(error);
    }
  });
  
  return (
    <div>
      <p>Data: {JSON.stringify(data)}</p>
      <button onClick={() => setData({ large: 'data'.repeat(1000000) })}>
        Try to Store Large Data (may fail)
      </button>
    </div>
  );
}

Common Types

// Storage serializer interface
interface Serializer<T> {
  read(value: string): T;
  write(value: T): string;
}

// Cookie attributes from js-cookie library
namespace Cookies {
  interface CookieAttributes {
    expires?: number | Date;
    path?: string;
    domain?: string;
    secure?: boolean;
    sameSite?: 'strict' | 'lax' | 'none';
    maxAge?: number;
    httpOnly?: boolean;
  }
}