React Storage Persist
Persistent state management for React. Store data reliably across sessions with automatic fallbacks, built-in TTL, and seamless TypeScript support.
Features
Unified API
Single interface for all storage engines
Automatic Fallbacks
Graceful degradation when unavailable
TTL Support
Built-in expiration with cleanup
React Hooks
First-class React integration
Type-Safe
Full TypeScript with type inference
Encryption
Optional encryption for sensitive data
Namespacing
Prefix/suffix support for keys
Event System
Subscribe to storage changes
Middleware
Extensible plugin system
Lightweight
Tree-shakeable, optimized bundle
๐ฆ Installation
npm install react-storage-persistyarn add react-storage-persistpnpm add react-storage-persist๐ Quick Start
Get up and running in seconds with our React hooks.
import { useStorage } from 'react-storage-persist';
function MyComponent() {
const [name, setName] = useStorage('user.name', 'Guest');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
);
}Core API Usage
import { createStorage } from 'react-storage-persist/core';
const storage = createStorage({
engine: 'localStorage',
prefix: 'myapp_',
ttl: 3600, // 1 hour in seconds
});
// Set data
await storage.set('user', { name: 'John', age: 30 });
// Get data
const user = await storage.get('user');
// Remove data
await storage.remove('user');
// Check existence
const exists = await storage.has('user');
// Get all keys
const keys = await storage.keys();๐๏ธ Storage Engines
The library supports multiple storage backends with automatic fallbacks.
LocalStorage
Persistent storage across browser sessions (~5-10MB limit).
Characteristics:
- Persistent across browser sessions
- ~5-10MB storage limit
- Synchronous API
- Domain-specific
const storage = createStorage({ engine: 'localStorage' });
await storage.set('settings', { theme: 'dark' });
const settings = await storage.get('settings');SessionStorage
Cleared when browser tab closes (~5-10MB limit).
Characteristics:
- Cleared when browser tab closes
- ~5-10MB storage limit
- Synchronous API
- Tab-specific
const storage = createStorage({ engine: 'sessionStorage' });
await storage.set('temp_data', { id: 123 });
const data = await storage.get('temp_data');IndexedDB
Large-scale structured data storage with asynchronous API.
Characteristics:
- Large storage capacity (100s of MBs)
- Asynchronous API
- Structured data support
- Best for large datasets
const storage = createStorage({ engine: 'indexedDB' });
await storage.set('large-dataset', { records: [...] });
const data = await storage.get('large-dataset');Memory (Fallback)
Always available, perfect for SSR and testing.
Characteristics:
- Always available (SSR-safe)
- Lost on page reload
- Unlimited capacity (RAM-based)
- Useful for testing
const storage = createStorage({ engine: 'memory' });
await storage.set('temp', { value: 'test' });
const temp = await storage.get('temp');Automatic Fallbacks
If the primary engine is unavailable, automatically falls back to the next available engine.
const storage = createStorage({
engine: 'localStorage',
fallback: ['sessionStorage', 'memory'],
});๐ช React Hooks
useStorage
Primary hook for persisting state to storage with automatic syncing.
import { useStorage } from 'react-storage-persist';
function Example() {
const [count, setCount] = useStorage('counter', 0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}With options:
const [data, setData] = useStorage('key', defaultValue, {
engine: 'localStorage',
ttl: 3600, // Expire after 1 hour
serializer: {
serialize: JSON.stringify,
deserialize: JSON.parse,
},
});useStorageState
Advanced state management with loading/error states and manual sync.
import { useStorageState } from 'react-storage-persist';
function Example() {
const [state, setState, { loading, error, sync }] = useStorageState('user', {
name: '',
email: '',
});
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<input
value={state.name}
onChange={(e) => setState({ ...state, name: e.target.value })}
/>
<button onClick={sync}>Refresh</button>
</div>
);
}useStorageReducer
Persist reducer state automatically.
import { useStorageReducer } from 'react-storage-persist';
type State = { count: number };
type Action = { type: 'increment' } | { type: 'decrement' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Example() {
const [state, dispatch] = useStorageReducer('counter', reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}useStorageSync
Sync state across multiple components and browser tabs.
import { useStorageSync } from 'react-storage-persist';
function ComponentA() {
const [theme, setTheme] = useStorageSync('theme', 'light');
return <button onClick={() => setTheme('dark')}>Switch Theme</button>;
}
function ComponentB() {
const [theme] = useStorageSync('theme', 'light');
return <div className={theme}>Current theme: {theme}</div>;
}useStorageEvent
Listen to storage change events.
import { useStorageEvent } from 'react-storage-persist';
function Example() {
useStorageEvent('user', (event) => {
console.log('User data changed!', event.newValue);
});
return <div>Listening to user changes...</div>;
}โ๏ธ Configuration
interface StorageConfig {
engine?: 'localStorage' | 'sessionStorage' | 'indexedDB' | 'memory';
prefix?: string; // Key prefix (e.g., 'myapp_')
suffix?: string; // Key suffix
ttl?: number; // Default TTL in seconds
fallback?: StorageEngine | StorageEngine[];
serializer?: {
serialize: (value: any) => string;
deserialize: (value: string) => any;
};
encrypt?: boolean;
encryptionKey?: string;
compression?: boolean;
onError?: (error: StorageError) => void;
onChange?: (event: StorageChangeEvent) => void;
debug?: boolean; // Enable debug logging
}const storage = createStorage({
engine: 'localStorage',
prefix: 'myapp_',
ttl: 86400, // 24 hours
fallback: ['sessionStorage', 'memory'],
debug: process.env.NODE_ENV === 'development',
onError: (error) => console.error('Storage error:', error),
});
// Set with custom TTL
await storage.set('session', userData, { ttl: 3600 }); // 1 hour๐ Middleware & Plugins
Using Middleware
import { createStorage } from 'react-storage-persist/core';
import { encryption, compression } from 'react-storage-persist/middleware';
const storage = createStorage({ engine: 'localStorage' });
// Add encryption middleware
storage.use(encryption({ key: 'your-secret-key' }));
// Add compression middleware
storage.use(compression({ threshold: 1024 })); // Compress if > 1KBCreating Custom Middleware
import type { Middleware } from 'react-storage-persist';
const logger: Middleware = {
name: 'logger',
beforeSet: async (key, value, options) => {
console.log(`Setting ${key}:`, value);
return value;
},
afterGet: async (key, value, options) => {
console.log(`Getting ${key}:`, value);
return value;
},
beforeRemove: async (key) => {
console.log(`Removing ${key}`);
},
};
storage.use(logger);๐ Event System
Subscribe to Changes
// Subscribe to specific key
const unsubscribe = storage.subscribe('user', (event) => {
console.log('User changed:', event.newValue);
});
// Subscribe to all changes
storage.subscribe((event) => {
console.log(`${event.type} on ${event.key}`);
});
// Cleanup
unsubscribe();๐ TypeScript Usage
Type-Safe Storage
interface User {
id: number;
name: string;
email: string;
}
// Type inference
const user = await storage.get<User>('user');
// user is User | null
// With default value
const user = await storage.get<User>('user', { id: 0, name: '', email: '' });
// user is User
// Setting with type safety
await storage.set<User>('user', {
id: 1,
name: 'John',
email: 'john@example.com',
});Typed Hooks
interface Settings {
theme: 'light' | 'dark';
notifications: boolean;
}
function Example() {
const [settings, setSettings] = useStorage<Settings>('settings', {
theme: 'light',
notifications: true,
});
// Type-safe updates
setSettings({ ...settings, theme: 'dark' });
}๐ Advanced Examples
Form State Persistence
import { useStorage } from 'react-storage-persist';
function SignupForm() {
const [formData, setFormData] = useStorage('signup-draft', {
email: '',
username: '',
password: '',
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await submitForm(formData);
setFormData({ email: '', username: '', password: '' });
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="Email"
/>
<input
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
placeholder="Username"
/>
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
placeholder="Password"
/>
<button type="submit">Sign Up</button>
</form>
);
}Shopping Cart with TTL
import { useStorage } from 'react-storage-persist';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
function ShoppingCart() {
const [cart, setCart] = useStorage<CartItem[]>('cart', [], {
ttl: 86400, // Cart expires after 24 hours
});
const addItem = (item: CartItem) => {
const existing = cart.find((i) => i.id === item.id);
if (existing) {
setCart(
cart.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
)
);
} else {
setCart([...cart, { ...item, quantity: 1 }]);
}
};
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
return (
<div>
<h2>Cart ({cart.length} items)</h2>
<ul>
{cart.map((item) => (
<li key={item.id}>
{item.name} ร {item.quantity} = ${item.price * item.quantity}
</li>
))}
</ul>
<p>Total: ${total.toFixed(2)}</p>
</div>
);
}Multi-Tab Synchronization
import { useStorageSync } from 'react-storage-persist';
function AuthStatus() {
const [isAuthenticated, setIsAuthenticated] = useStorageSync(
'auth.status',
false
);
const login = () => setIsAuthenticated(true);
const logout = () => setIsAuthenticated(false);
return (
<div>
<p>Status: {isAuthenticated ? 'Logged In' : 'Logged Out'}</p>
{isAuthenticated ? (
<button onClick={logout}>Logout</button>
) : (
<button onClick={login}>Login</button>
)}
<small>Open this page in multiple tabs to see sync in action!</small>
</div>
);
}IndexedDB for Large Data
import { createStorage } from 'react-storage-persist/core';
import { useEffect, useState } from 'react';
const storage = createStorage({ engine: 'indexedDB' });
function DataManager() {
const [records, setRecords] = useState<any[]>([]);
useEffect(() => {
loadRecords();
}, []);
const loadRecords = async () => {
const data = await storage.get('large-dataset', []);
setRecords(data);
};
const saveRecords = async (newRecords: any[]) => {
await storage.set('large-dataset', newRecords);
setRecords(newRecords);
};
return (
<div>
<h2>Managing {records.length} records</h2>
{/* Your UI here */}
</div>
);
}๐ API Reference
Core Storage API
createStorage(config?: StorageConfig): StorageCreate a new storage instance.
get<T>(key: string, defaultValue?: T): Promise<T | null>Retrieve a value from storage.
set<T>(key: string, value: T, options?: StorageOptions): Promise<void>Store a value in storage.
remove(key: string): Promise<void>Remove a key from storage.
clear(): Promise<void>Clear all keys (respecting prefix/suffix).
has(key: string): Promise<boolean>Check if a key exists.
keys(): Promise<string[]>Get all keys (without prefix/suffix).
size(): Promise<number>Get approximate storage size in bytes.
subscribe(key: string | callback, callback?): () => voidSubscribe to storage changes. Returns unsubscribe function.
use(middleware: Middleware): voidAdd middleware to the storage instance.
Hooks API
useStorage<T>(key, defaultValue, options?): [T, (value: T) => void]Persist state to storage with automatic syncing.
useStorageState<T>(key, defaultValue, options?): [T, Dispatch<T>, Utils]Advanced state management with loading/error states.
useStorageReducer<S, A>(key, reducer, initialState, options?): [S, Dispatch<A>]Persist reducer state automatically.
useStorageSync<T>(key, defaultValue, options?): [T, (value: T) => void]Synchronize state across components and tabs.
useStorageEvent(key, callback): voidListen to storage change events.
๐งช Testing
The library includes comprehensive test coverage.
# Unit tests
npm run test
# Unit tests with UI
npm run test:ui
# Coverage report
npm run test:coverage
# E2E tests
npm run test:e2eTesting Your Code
import { createStorage } from 'react-storage-persist/core';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
// Mock storage for tests
const storage = createStorage({ engine: 'memory' });
test('persists user input', async () => {
const user = userEvent.setup();
render(<MyComponent storage={storage} />);
const input = screen.getByRole('textbox');
await user.type(input, 'Hello');
const value = await storage.get('user-input');
expect(value).toBe('Hello');
});๐ค Contributing
Contributions are welcome! Here's how to get started:
# Clone the repository
git clone https://github.com/ienouali/react-storage-persist.git
cd react-storage-persist
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run buildAbout Developer
๐ License
MIT ยฉ ienouali. This library is open source and available for everyone to use.