Browser storage integration hooks that provide automatic serialization, state synchronization, and seamless integration with localStorage, sessionStorage, and cookies.
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>
);
}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;
}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>
);
}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>
);
}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>
);
}// 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;
}
}