← Back to Tutorials Chapter 22

Complexity Management

State Management

Managing application state in a PWA requires handling both online and offline states, sync status, and multiple clients.

// Centralized state management for PWAs
const PWAState = {
  _state: {
    online: navigator.onLine,
    syncNeeded: false,
    pendingActions: [],
    lastSync: null,
    user: null,
    settings: {}
  },

  _listeners: [],

  get(key) {
    return this._state[key];
  },

  set(key, value) {
    this._state[key] = value;
    this._notify(key, value);
  },

  subscribe(listener) {
    this._listeners.push(listener);
    return () => {
      this._listeners = this._listeners.filter(l => l !== listener);
    };
  },

  _notify(key, value) {
    this._listeners.forEach(listener => {
      try {
        listener(key, value, this._state);
      } catch (err) {
        console.error('State listener error:', err);
      }
    });
  },

  // Persist state to localStorage
  persist() {
    localStorage.setItem('pwa_state', JSON.stringify(this._state));
  },

  // Restore state from localStorage
  restore() {
    try {
      const saved = localStorage.getItem('pwa_state');
      if (saved) {
        this._state = { ...this._state, ...JSON.parse(saved) };
      }
    } catch (err) {
      console.error('Failed to restore state:', err);
    }
  }
};

// Online/Offline tracking
window.addEventListener('online', () => {
  PWAState.set('online', true);
  syncPendingActions();
});

window.addEventListener('offline', () => {
  PWAState.set('online', false);
});

Code Organization

// Recommended PWA project structure
my-pwa/
  src/
    app/
      shell.js          # App shell logic
      router.js         # Client-side routing
      state.js          # State management
    sw/
      sw.js             # Service Worker entry
      caching.js        # Caching strategies
      sync.js           # Background sync
      push.js           # Push notifications
    pages/
      home/
        index.html
        home.js
        home.css
      settings/
        index.html
        settings.js
        settings.css
    components/
      header.js
      footer.js
      offline-banner.js
    utils/
      db.js             # IndexedDB helpers
      network.js        # Network detection
      features.js       # Feature detection
    assets/
      images/
      fonts/
      icons/
  manifest.json
  index.html

Bundling and Build Tools

// Vite config for PWA
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['fonts/*.woff2', 'images/*.{png,jpg,webp}'],
      manifest: {
        name: 'My PWA',
        short_name: 'MyPWA',
        display: 'standalone',
        theme_color: '#4f46e5',
        icons: [
          { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' }
        ]
      },
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,jpg,webp,woff2}'],
        runtimeCaching: [
          {
            urlPattern: /\/api\//,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: { maxEntries: 50, maxAgeSeconds: 300 }
            }
          }
        ]
      }
    })
  ]
});

Complexity Patterns

Exercise: Refactor a PWA with messy state management into a clean architecture using the state management pattern above. Add online/offline detection, persist state to localStorage, and ensure the UI reactively updates when the state changes.