react-vault-store
v1.0.1
Published
A lightweight, type-safe state management library for React with built-in IndexedDB persistence support.
Maintainers
Keywords
Readme
React Valut Store
A lightweight, type-safe state management library for React with built-in IndexedDB persistence support.
Features
- Type-Safe: Full TypeScript support with complete type inference
- Persistent Storage: Optional IndexedDB persistence for automatic state synchronization
- Lightweight: Minimal bundle size with zero dependencies (except React)
- Familiar API: Similar to React's
useStatehook - easy to learn and use - Simple Setup: Create stores in seconds with minimal boilerplate
- Cross-Component Sync: Share state across components automatically
- Performant: Optimized re-renders with React's built-in mechanisms
Installation
npm install react-vault-storeyarn add react-vault-storepnpm add react-vault-storeQuick Start
Non-Persistent Store
import { createStore } from 'react-vault-store';
interface CounterState {
count: number;
lastUpdated: Date;
}
const useCounter = createStore<CounterState>('counter', {
count: 0,
lastUpdated: new Date(),
});
// Use in your components
function Counter() {
const [state, setState] = useCounter();
const increment = () => {
setState(prev => ({
...prev,
count: prev.count + 1,
lastUpdated: new Date(),
}));
};
return (
<div>
<p>Count: {state.count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}Persistent Store
import { createStorePersist } from 'react-vault-store';
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
}
const usePreferences = createStorePersist<UserPreferences>('preferences', {
theme: 'light',
language: 'en',
notifications: true,
});
function Settings() {
const [prefs, setPrefs] = usePreferences();
const toggleTheme = () => {
setPrefs(prev => ({
...prev,
theme: prev.theme === 'light' ? 'dark' : 'light',
}));
};
return (
<div>
<p>Current Theme: {prefs.theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}API Reference
createStore<T>(key: string, initialState: T)
Creates a non-persistent store that exists only in memory during the session.
Parameters:
key: Unique identifier for the storeinitialState: Initial state value
Returns: A custom React hook that returns [state, setState]
Example:
interface TodosState {
items: Array<{ id: string; text: string; completed: boolean }>;
filter: 'all' | 'active' | 'completed';
}
const useTodos = createStore<TodosState>('todos', {
items: [],
filter: 'all',
});createStorePersist<T>(key: string, initialState: T)
Creates a persistent store that automatically syncs with IndexedDB.
Parameters:
key: Unique identifier for the store (also used as IndexedDB key)initialState: Initial state value (used if no persisted data exists)
Returns: A custom React hook that returns [state, setState]
Example:
interface ShoppingCart {
items: Array<{ id: string; name: string; quantity: number; price: number }>;
total: number;
}
const useCart = createStorePersist<ShoppingCart>('shopping-cart', {
items: [],
total: 0,
});Advanced Examples
Complex State Management
import { createStorePersist } from 'react-vault-store';
interface AppState {
user: {
id: string;
name: string;
email: string;
avatar?: string;
} | null;
settings: {
theme: 'light' | 'dark' | 'auto';
sidebarCollapsed: boolean;
fontSize: 'small' | 'medium' | 'large';
};
metadata: {
lastLogin: string;
sessionCount: number;
};
}
const useAppState = createStorePersist<AppState>('app-state', {
user: null,
settings: {
theme: 'auto',
sidebarCollapsed: false,
fontSize: 'medium',
},
metadata: {
lastLogin: new Date().toISOString(),
sessionCount: 0,
},
});
// Login component
function LoginForm() {
const [state, setState] = useAppState();
const handleLogin = async (userData: AppState['user']) => {
setState(prev => ({
...prev,
user: userData,
metadata: {
lastLogin: new Date().toISOString(),
sessionCount: prev.metadata.sessionCount + 1,
},
}));
};
// ... rest of component
}
// Settings component (auto-synced across tabs!)
function SettingsPanel() {
const [state, setState] = useAppState();
const updateTheme = (theme: AppState['settings']['theme']) => {
setState(prev => ({
...prev,
settings: { ...prev.settings, theme },
}));
};
return (
<select
value={state.settings.theme}
onChange={(e) => updateTheme(e.target.value as any)}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="auto">Auto</option>
</select>
);
}Form State with Validation
import { createStore } from 'react-vault-store';
interface FormState {
values: {
email: string;
password: string;
confirmPassword: string;
};
errors: {
email?: string;
password?: string;
confirmPassword?: string;
};
touched: {
email: boolean;
password: boolean;
confirmPassword: boolean;
};
isSubmitting: boolean;
}
const useRegistrationForm = createStore<FormState>('registration-form', {
values: { email: '', password: '', confirmPassword: '' },
errors: {},
touched: { email: false, password: false, confirmPassword: false },
isSubmitting: false,
});
function RegistrationForm() {
const [form, setForm] = useRegistrationForm();
const handleChange = (field: keyof FormState['values']) => (
e: React.ChangeEvent<HTMLInputElement>
) => {
setForm(prev => ({
...prev,
values: { ...prev.values, [field]: e.target.value },
touched: { ...prev.touched, [field]: true },
}));
};
const validate = () => {
const errors: FormState['errors'] = {};
if (!form.values.email.includes('@')) {
errors.email = 'Invalid email address';
}
if (form.values.password.length < 8) {
errors.password = 'Password must be at least 8 characters';
}
if (form.values.password !== form.values.confirmPassword) {
errors.confirmPassword = 'Passwords do not match';
}
setForm(prev => ({ ...prev, errors }));
return Object.keys(errors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validate()) return;
setForm(prev => ({ ...prev, isSubmitting: true }));
// Submit logic here
setForm(prev => ({ ...prev, isSubmitting: false }));
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={form.values.email}
onChange={handleChange('email')}
/>
{form.touched.email && form.errors.email && (
<span>{form.errors.email}</span>
)}
{/* ... rest of form */}
</form>
);
}Multi-Store Pattern
// stores/auth.ts
export const useAuth = createStorePersist<AuthState>('auth', {
token: null,
user: null,
isAuthenticated: false,
});
// stores/cart.ts
export const useCart = createStorePersist<CartState>('cart', {
items: [],
total: 0,
});
// stores/ui.ts
export const useUI = createStore<UIState>('ui', {
sidebarOpen: true,
modalStack: [],
notifications: [],
});
// Usage in component
function Dashboard() {
const [auth] = useAuth();
const [cart] = useCart();
const [ui, setUI] = useUI();
return (
<div>
<p>Welcome, {auth.user?.name}</p>
<p>Cart Items: {cart.items.length}</p>
<button onClick={() => setUI(prev => ({
...prev,
sidebarOpen: !prev.sidebarOpen
}))}>
Toggle Sidebar
</button>
</div>
);
}TypeScript Support
The library is written in TypeScript and provides full type inference:
interface Product {
id: number;
name: string;
price: number;
category: string;
}
const useProducts = createStore<Product[]>('products', []);
function ProductList() {
const [products, setProducts] = useProducts();
// TypeScript knows products is Product[]
const addProduct = (product: Product) => {
setProducts(prev => [...prev, product]);
};
// Type checking works perfectly
const expensiveProducts = products.filter(p => p.price > 100);
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}When to Use Persistent vs Non-Persistent
Use createStore (Non-Persistent) for:
- Session-only data (current page, temporary UI state)
- Form drafts that should reset on page reload
- Real-time data that changes frequently
- Data that shouldn't persist across sessions
Use createStorePersist (Persistent) for:
- User preferences and settings
- Authentication tokens and user data
- Shopping cart contents
- Draft content (posts, messages)
- Application configuration
- Any data that should survive page reloads
Browser Compatibility
- Non-Persistent Stores: All browsers that support React
- Persistent Stores: All browsers that support IndexedDB (all modern browsers)
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
