Persistence Examples
This page provides practical examples of implementing persistence in various real-world scenarios using QuantaJS.
User Preferences Store
A common use case for persistence is storing user preferences and settings:
import { createStore, LocalStorageAdapter } from '@quantajs/core';
const userPreferencesStore = createStore('user-preferences', {
state: () => ({
theme: 'light',
language: 'en',
fontSize: 'medium',
sidebarCollapsed: false,
notifications: {
email: true,
push: false,
sms: false
},
accessibility: {
highContrast: false,
reduceMotion: false,
screenReader: false
}
}),
persist: {
adapter: new LocalStorageAdapter('user-preferences-v2'),
version: 2,
debounceMs: 300,
migrations: {
2: (data) => ({
...data,
accessibility: {
highContrast: false,
reduceMotion: false,
screenReader: false,
...data.accessibility
}
})
},
onError: (error, operation) => {
console.warn(`Failed to ${operation} user preferences:`, error);
}
},
actions: {
updateTheme(theme) {
this.theme = theme;
// Automatically saved to localStorage
},
toggleSidebar() {
this.sidebarCollapsed = !this.sidebarCollapsed;
// Automatically saved to localStorage
},
updateNotificationSettings(type, enabled) {
this.notifications[type] = enabled;
// Automatically saved to localStorage
}
}
});
// Usage in components
function ThemeToggle() {
const theme = userPreferencesStore.theme;
return (
<button onClick={() => userPreferencesStore.updateTheme(theme === 'light' ? 'dark' : 'light')}>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
);
}
Shopping Cart Persistence
Persist shopping cart items across browser sessions:
const shoppingCartStore = createStore('shopping-cart', {
state: () => ({
items: [],
appliedCoupons: [],
shippingAddress: null,
billingAddress: null,
lastUpdated: null
}),
persist: {
adapter: new LocalStorageAdapter('shopping-cart-v1'),
version: 1,
debounceMs: 200,
include: ['items', 'appliedCoupons'], // Don't persist addresses
exclude: ['shippingAddress', 'billingAddress'],
validator: (data) => {
// Ensure items have required properties
if (data.items && Array.isArray(data.items)) {
return data.items.every(item =>
item.id &&
typeof item.quantity === 'number' &&
item.quantity > 0 &&
item.price > 0
);
}
return true;
},
onError: (error, operation) => {
if (operation === 'read' && !store.validator(data)) {
// Clear invalid cart data
store.items = [];
store.appliedCoupons = [];
store.lastUpdated = Date.now();
}
}
},
getters: {
totalItems: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),
totalPrice: (state) => state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0)
},
actions: {
addItem(product, quantity = 1) {
const existingItem = this.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.items.push({
id: product.id,
name: product.name,
price: product.price,
quantity,
addedAt: Date.now()
});
}
this.lastUpdated = Date.now();
// Automatically saved to localStorage
},
removeItem(productId) {
this.items = this.items.filter(item => item.id !== productId);
this.lastUpdated = Date.now();
// Automatically saved to localStorage
},
updateQuantity(productId, quantity) {
const item = this.items.find(item => item.id === productId);
if (item) {
if (quantity <= 0) {
this.removeItem(productId);
} else {
item.quantity = quantity;
this.lastUpdated = Date.now();
}
}
},
clearCart() {
this.items = [];
this.appliedCoupons = [];
this.lastUpdated = Date.now();
// Automatically saved to localStorage
}
}
});
Form Data Persistence
Save form progress and drafts automatically:
const formStore = createStore('application-form', {
state: () => ({
currentStep: 1,
formData: {
personalInfo: {
firstName: '',
lastName: '',
email: '',
phone: ''
},
education: {
degree: '',
institution: '',
graduationYear: ''
},
experience: {
company: '',
position: '',
startDate: '',
endDate: '',
description: ''
}
},
isComplete: false,
lastSaved: null
}),
persist: {
adapter: new SessionStorageAdapter('application-form-draft'),
version: 1,
debounceMs: 1000, // Save after 1 second of inactivity
include: ['currentStep', 'formData'],
exclude: ['isComplete', 'lastSaved'],
onError: (error, operation) => {
if (operation === 'write') {
console.warn('Failed to save form draft:', error);
// Optionally show user notification
showNotification('Failed to save draft', 'warning');
}
}
},
actions: {
updateField(section, field, value) {
this.formData[section][field] = value;
this.lastSaved = Date.now();
// Automatically saved to sessionStorage
},
nextStep() {
if (this.currentStep < 3) {
this.currentStep++;
this.lastSaved = Date.now();
}
},
previousStep() {
if (this.currentStep > 1) {
this.currentStep--;
this.lastSaved = Date.now();
}
},
validateStep(step) {
const stepData = this.formData[Object.keys(this.formData)[step - 1]];
return Object.values(stepData).every(value => value.trim() !== '');
},
submitForm() {
if (this.validateStep(this.currentStep)) {
this.isComplete = true;
this.lastSaved = Date.now();
// Clear draft after successful submission
this.$persist?.clear();
return true;
}
return false;
}
}
});
// Usage in form components
function PersonalInfoForm() {
const { personalInfo } = formStore.formData;
const handleChange = (field, value) => {
formStore.updateField('personalInfo', field, value);
};
return (
<form>
<input
type="text"
value={personalInfo.firstName}
onChange={(e) => handleChange('firstName', e.target.value)}
placeholder="First Name"
/>
<input
type="text"
value={personalInfo.lastName}
onChange={(e) => handleChange('lastName', e.target.value)}
placeholder="Last Name"
/>
{/* More fields... */}
</form>
);
}
Application State Persistence
Persist essential application state across sessions:
const appStateStore = createStore('app-state', {
state: () => ({
currentRoute: '/',
sidebarOpen: false,
modals: [],
notifications: [],
user: null,
permissions: [],
lastActivity: Date.now()
}),
persist: {
adapter: new LocalStorageAdapter('app-state-v1'),
version: 1,
debounceMs: 1000,
include: ['currentRoute', 'sidebarOpen', 'user'],
exclude: ['modals', 'notifications', 'permissions', 'lastActivity'],
onError: (error, operation) => {
console.error(`App state persistence ${operation} failed:`, error);
// Don't break the app on persistence errors
if (operation === 'read') {
// Use default values
store.currentRoute = '/';
store.sidebarOpen = false;
}
}
},
actions: {
navigateTo(route) {
this.currentRoute = route;
this.lastActivity = Date.now();
// Automatically saved to localStorage
},
toggleSidebar() {
this.sidebarOpen = !this.sidebarOpen;
this.lastActivity = Date.now();
// Automatically saved to localStorage
},
setUser(userData) {
this.user = userData;
this.permissions = userData?.permissions || [];
this.lastActivity = Date.now();
// Automatically saved to localStorage
},
clearUser() {
this.user = null;
this.permissions = [];
this.lastActivity = Date.now();
// Automatically saved to localStorage
},
addNotification(message, type = 'info') {
this.notifications.push({
id: Date.now(),
message,
type,
timestamp: Date.now()
});
// Don't persist notifications
},
removeNotification(id) {
this.notifications = this.notifications.filter(n => n.id !== id);
// Don't persist notifications
}
}
});
Offline Data Store
Handle offline scenarios with IndexedDB for larger datasets:
const offlineDataStore = createStore('offline-data', {
state: () => ({
documents: [],
images: [],
syncStatus: 'idle', // 'idle', 'syncing', 'error'
lastSync: null,
pendingChanges: []
}),
persist: {
adapter: new IndexedDBAdapter('offline-data', 'app-db', 'stores', 1),
version: 1,
debounceMs: 500,
include: ['documents', 'images', 'syncStatus', 'lastSync'],
exclude: ['pendingChanges'],
onError: (error, operation) => {
console.error(`Offline data ${operation} failed:`, error);
if (operation === 'write') {
// Queue for later sync
store.pendingChanges.push({
operation: 'write',
timestamp: Date.now(),
error: error.message
});
}
}
},
actions: {
addDocument(doc) {
this.documents.push({
...doc,
id: Date.now(),
createdAt: Date.now(),
modifiedAt: Date.now()
});
this.syncStatus = 'pending';
// Automatically saved to IndexedDB
},
updateDocument(id, updates) {
const doc = this.documents.find(d => d.id === id);
if (doc) {
Object.assign(doc, updates, { modifiedAt: Date.now() });
this.syncStatus = 'pending';
// Automatically saved to IndexedDB
}
},
removeDocument(id) {
this.documents = this.documents.filter(d => d.id !== id);
this.syncStatus = 'pending';
// Automatically saved to IndexedDB
},
async syncWithServer() {
if (this.syncStatus === 'syncing') return;
this.syncStatus = 'syncing';
try {
// Sync documents
const response = await fetch('/api/documents/sync', {
method: 'POST',
body: JSON.stringify({
documents: this.documents,
lastSync: this.lastSync
})
});
if (response.ok) {
const result = await response.json();
this.documents = result.documents;
this.lastSync = Date.now();
this.syncStatus = 'idle';
this.pendingChanges = [];
} else {
throw new Error('Sync failed');
}
} catch (error) {
this.syncStatus = 'error';
console.error('Sync failed:', error);
}
}
}
});
Cross-tab Synchronization Example
Demonstrate automatic synchronization across browser tabs:
const sharedStateStore = createStore('shared-state', {
state: () => ({
currentUser: null,
activeUsers: [],
chatMessages: [],
systemStatus: 'online'
}),
persist: {
adapter: new LocalStorageAdapter('shared-state-v1'),
version: 1,
debounceMs: 100,
onError: (error, operation) => {
console.warn(`Shared state ${operation} failed:`, error);
}
},
actions: {
setCurrentUser(user) {
this.currentUser = user;
this.activeUsers = this.activeUsers.filter(u => u.id !== user.id);
this.activeUsers.push({
...user,
lastSeen: Date.now()
});
// Automatically synchronized across tabs
},
addChatMessage(message) {
this.chatMessages.push({
...message,
id: Date.now(),
timestamp: Date.now()
});
// Automatically synchronized across tabs
},
updateSystemStatus(status) {
this.systemStatus = status;
// Automatically synchronized across tabs
}
}
});
// Usage in chat application
function ChatComponent() {
const { chatMessages, currentUser, activeUsers } = sharedStateStore;
const sendMessage = (text) => {
sharedStateStore.addChatMessage({
text,
userId: currentUser.id,
userName: currentUser.name
});
};
return (
<div>
<div className="active-users">
{activeUsers.map(user => (
<span key={user.id} className="user-indicator">
{user.name}
</span>
))}
</div>
<div className="chat-messages">
{chatMessages.map(message => (
<div key={message.id} className="message">
<strong>{message.userName}:</strong> {message.text}
</div>
))}
</div>
<input
type="text"
onKeyPress={(e) => {
if (e.key === 'Enter') {
sendMessage(e.target.value);
e.target.value = '';
}
}}
placeholder="Type a message..."
/>
</div>
);
}
Learn More
- Persistence Guide - Comprehensive guide to using persistence
- Persistence API - Complete API reference
- Best Practices - Persistence best practices
- Managing Stores - Store management patterns