React API Reference
This document provides comprehensive API documentation for the @sitharaj08/react-unified-storage
package, which provides React bindings for the core storage library.
Table of Contents
- StorageProvider
- useStore
- useStoreSuspense
- useCollection
- Type Definitions
- Error Handling
- Best Practices
StorageProvider
Component Overview
The StorageProvider
component provides the storage context to your React application. It must wrap any components that use storage hooks.
import { StorageProvider } from '@sitharaj08/react-unified-storage';
function App() {
return (
<StorageProvider config={{ driver: 'auto' }}>
<MyComponent />
</StorageProvider>
);
}
Props
interface StorageProviderProps {
/** Storage configuration */
config: StorageConfig;
/** Child components */
children: React.ReactNode;
}
Configuration
The config
prop accepts a StorageConfig
object identical to the core library:
<StorageProvider config={{
driver: 'auto', // Storage driver
namespace: 'myapp', // Key prefix
broadcast: true, // Cross-tab sync
encryption: { // AES-GCM encryption
key: 'your-secret-key',
salt: 'optional-salt'
},
compression: true, // Gzip compression
errorHandler: console.error
}}>
<App />
</StorageProvider>
Multiple Providers
You can use multiple providers with different configurations in the same app:
function App() {
return (
<StorageProvider config={{ driver: 'memory', namespace: 'temp' }}>
<TemporaryData />
</StorageProvider>
<StorageProvider config={{ driver: 'idb', namespace: 'persistent' }}>
<PersistentData />
</StorageProvider>
);
}
useStore
Hook Signature
const [value, setValue, meta] = useStore<T>(key, options?);
The primary hook for reading and writing storage data with automatic re-rendering.
Parameters
key: string
- Storage keyoptions?: UseStoreOptions<T>
- Optional configuration
Returns
value: T
- Current stored value (or defaultValue)setValue: (value: T | ((prev: T) => T)) => Promise<void>
- Update functionmeta?: StorageMeta
- Metadata (whenmetadata: true
)
Options
interface UseStoreOptions<T> {
/** Default value when no data is stored */
defaultValue?: T;
/** Zod schema for validation */
schema?: z.ZodSchema<T>;
/** Data version for migrations */
version?: number;
/** Include metadata in return value */
metadata?: boolean;
/** Time to live in milliseconds */
ttlMs?: number;
}
Basic Usage
import { useStore } from '@sitharaj08/react-unified-storage';
function Counter() {
const [count, setCount] = useStore('counter', { defaultValue: 0 });
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
Functional Updates
function Counter() {
const [count, setCount] = useStore('counter', { defaultValue: 0 });
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => Math.max(0, prev - 1));
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
Schema Validation
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).max(150)
});
function UserProfile() {
const [user, setUser] = useStore('user', {
schema: userSchema,
defaultValue: { name: '', email: '', age: 0 }
});
// TypeScript knows user has name, email, and age properties
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<p>Age: {user.age}</p>
</div>
);
}
With Metadata
function DataWithMeta() {
const [data, setData, meta] = useStore('mydata', {
metadata: true,
defaultValue: 'Hello World'
});
return (
<div>
<p>Data: {data}</p>
{meta && (
<div>
<p>Created: {new Date(meta.createdAt).toLocaleString()}</p>
<p>Updated: {new Date(meta.updatedAt).toLocaleString()}</p>
<p>Driver: {meta.driver}</p>
<p>Version: {meta.version}</p>
</div>
)}
</div>
);
}
TTL (Time To Live)
function TempNotification() {
const [message, setMessage] = useStore('notification', {
ttlMs: 5000, // Expires in 5 seconds
defaultValue: null
});
const showNotification = (text: string) => {
setMessage(text);
// Message will automatically disappear after 5 seconds
};
return (
<div>
<button onClick={() => showNotification('Hello!')}>
Show Notification
</button>
{message && <div className="notification">{message}</div>}
</div>
);
}
useStoreSuspense
Hook Signature
const [value, setValue] = useStoreSuspense<T>(key, options);
Suspense-enabled version of useStore
for integration with React Suspense.
Parameters
Same as useStore
, but defaultValue
is required.
Returns
value: T
- Current stored valuesetValue: (value: T | ((prev: T) => T)) => Promise<void>
- Update function
Usage with Suspense
import { Suspense } from 'react';
import { useStoreSuspense } from '@sitharaj08/react-unified-storage';
function AsyncData() {
const [data, setData] = useStoreSuspense('async-data', {
defaultValue: 'Loading...'
});
return <div>{data}</div>;
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<AsyncData />
</Suspense>
);
}
When to Use
- When you want to leverage React's Suspense for loading states
- For server-side rendering with data fetching
- When integrating with data fetching libraries that support Suspense
useCollection
Hook Signature
const collection = useCollection<T>(name, options?);
Hook for working with typed collections of data.
Type Requirements
The generic type T
must include an id
field:
interface Todo {
id: number;
title: string;
completed: boolean;
}
interface User {
id: string;
name: string;
email: string;
}
Returns
A collection object with the following methods:
interface CollectionHook<T extends { id: string | number }> {
// Add a new item
add(item: Omit<T, 'id'>): Promise<T>;
// Get an item by ID
get(id: string | number): Promise<T | null>;
// Update an existing item
update(id: string | number, updates: Partial<T>): Promise<T>;
// Remove an item
remove(id: string | number): Promise<void>;
// List all items with optional filter
list(filter?: (item: T) => boolean): Promise<T[]>;
// Clear all items
clear(): Promise<void>;
}
Basic Usage
import { useCollection } from '@sitharaj08/react-unified-storage';
interface Todo {
id: number;
title: string;
completed: boolean;
}
function TodoApp() {
const todos = useCollection<Todo>('todos');
const [todoList, setTodoList] = useState<Todo[]>([]);
const loadTodos = async () => {
const allTodos = await todos.list();
setTodoList(allTodos);
};
const addTodo = async (title: string) => {
await todos.add({ title, completed: false });
loadTodos(); // Refresh list
};
const toggleTodo = async (id: number) => {
const todo = await todos.get(id);
if (todo) {
await todos.update(id, { completed: !todo.completed });
loadTodos();
}
};
useEffect(() => {
loadTodos();
}, []);
return (
<div>
<button onClick={() => addTodo('New Todo')}>Add Todo</button>
{todoList.map(todo => (
<div key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.title}
</span>
</div>
))}
</div>
);
}
Advanced Collection Usage
function TodoApp() {
const todos = useCollection<Todo>('todos');
// Filter completed todos
const [completedTodos, setCompletedTodos] = useState<Todo[]>([]);
const loadCompleted = async () => {
const completed = await todos.list(todo => todo.completed);
setCompletedTodos(completed);
};
// Bulk operations
const markAllComplete = async () => {
const allTodos = await todos.list();
await Promise.all(
allTodos.map(todo =>
todos.update(todo.id, { completed: true })
)
);
loadCompleted();
};
const clearCompleted = async () => {
const completed = await todos.list(todo => todo.completed);
await Promise.all(
completed.map(todo => todos.remove(todo.id))
);
loadCompleted();
};
return (
<div>
<button onClick={markAllComplete}>Mark All Complete</button>
<button onClick={clearCompleted}>Clear Completed</button>
<h3>Completed Todos ({completedTodos.length})</h3>
{completedTodos.map(todo => (
<div key={todo.id}>{todo.title}</div>
))}
</div>
);
}
Type Definitions
UseStoreOptions<T>
interface UseStoreOptions<T> {
defaultValue?: T;
schema?: z.ZodSchema<T>;
version?: number;
metadata?: boolean;
ttlMs?: number;
}
StorageMeta
interface StorageMeta {
createdAt: number;
updatedAt: number;
expiresAt?: number;
driver: StorageDriver;
version: number;
}
StorageDriver
type StorageDriver = 'memory' | 'local' | 'session' | 'idb' | 'auto';
Error Handling
Hook Error Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function StorageErrorFallback({ error, resetErrorBoundary }) {
return (
<div>
<h2>Storage Error</h2>
<p>{error.message}</p>
<button onClick={resetErrorBoundary}>Retry</button>
</div>
);
}
function App() {
return (
<ErrorBoundary FallbackComponent={StorageErrorFallback}>
<StorageProvider config={{ driver: 'auto' }}>
<MyComponent />
</StorageProvider>
</ErrorBoundary>
);
}
Async Error Handling
function UserProfile() {
const [user, setUser] = useStore('user', {
defaultValue: { name: 'Anonymous' }
});
const updateUser = async (newData) => {
try {
await setUser({ ...user, ...newData });
} catch (error) {
console.error('Failed to update user:', error);
// Handle error (show toast, etc.)
}
};
return (
<div>
<input
value={user.name}
onChange={(e) => updateUser({ name: e.target.value })}
/>
</div>
);
}
Best Practices
Provider Placement
Place StorageProvider
high in your component tree:
// ✅ Good
function App() {
return (
<StorageProvider config={{ driver: 'auto' }}>
<Router>
<Routes />
</Router>
</StorageProvider>
);
}
// ❌ Avoid
function DeepComponent() {
return (
<StorageProvider config={{ driver: 'auto' }}>
<div>Only this component has access</div>
</StorageProvider>
);
}
Key Naming
Use consistent key naming conventions:
// ✅ Good
const [user, setUser] = useStore('user-profile');
const [settings, setSettings] = useStore('app-settings');
const todos = useCollection('user-todos');
// ❌ Avoid
const [data, setData] = useStore('d');
const [stuff, setStuff] = useStore('myStuff123');
Default Values
Always provide meaningful default values:
// ✅ Good
const [user, setUser] = useStore('user', {
defaultValue: { name: '', email: '', preferences: {} }
});
// ❌ Avoid
const [user, setUser] = useStore('user'); // user could be undefined
Schema Validation
Use Zod schemas for type safety and validation:
// ✅ Good
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).max(150).optional()
});
const [user, setUser] = useStore('user', {
schema: userSchema,
defaultValue: { name: '', email: '' }
});
// TypeScript now knows the exact shape of user
Performance Considerations
- Avoid storing large objects in hooks that re-render frequently
- Use
useMemo
for expensive computations on stored data - Consider using
useCollection
for lists of items - Use functional updates when depending on previous values
// ✅ Good
const [count, setCount] = useStore('counter', { defaultValue: 0 });
const increment = () => setCount(prev => prev + 1);
// ❌ Avoid frequent re-renders with large objects
const [largeData, setLargeData] = useStore('large-dataset');
const processedData = useMemo(() => expensiveOperation(largeData), [largeData]);
Cross-Tab Synchronization
Enable broadcasting for multi-tab applications:
<StorageProvider config={{
broadcast: true, // Enable cross-tab sync
driver: 'idb' // Use a persistent driver
}}>
<App />
</StorageProvider>
Testing
import { render, screen, waitFor } from '@testing-library/react';
import { StorageProvider } from '@sitharaj08/react-unified-storage';
const renderWithStorage = (component, config = { driver: 'memory' }) => {
return render(
<StorageProvider config={config}>
{component}
</StorageProvider>
);
};
test('loads and displays data', async () => {
renderWithStorage(<MyComponent />);
await waitFor(() => {
expect(screen.getByText('Expected Content')).toBeInTheDocument();
});
});
For core library APIs, see the Core API Reference.