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