Skip to content

Caching

Implementing caching strategies with Nexus State.

Basic In-Memory Cache

javascript
import { atom, createStore } from '@nexus-state/core';

// Simple cache implementation
class SimpleCache {
  constructor() {
    this.cache = new Map();
    this.timeouts = new Map();
  }
  
  get(key) {
    return this.cache.get(key);
  }
  
  set(key, value, ttl = 5 * 60 * 1000) { // 5 minutes default
    this.cache.set(key, value);
    
    // Clear existing timeout
    if (this.timeouts.has(key)) {
      clearTimeout(this.timeouts.get(key));
    }
    
    // Set expiration timeout
    const timeout = setTimeout(() => {
      this.cache.delete(key);
      this.timeouts.delete(key);
    }, ttl);
    
    this.timeouts.set(key, timeout);
  }
  
  has(key) {
    return this.cache.has(key);
  }
  
  delete(key) {
    this.cache.delete(key);
    
    if (this.timeouts.has(key)) {
      clearTimeout(this.timeouts.get(key));
      this.timeouts.delete(key);
    }
  }
  
  clear() {
    this.cache.clear();
    
    for (const timeout of this.timeouts.values()) {
      clearTimeout(timeout);
    }
    this.timeouts.clear();
  }
}

const cache = new SimpleCache();
const cacheAtom = atom(cache);

const store = createStore();

// Cache-aware data fetching
const fetchWithCache = async (key, fetcher, ttl) => {
  // Check cache first
  if (cache.has(key)) {
    return cache.get(key);
  }
  
  // Fetch data
  const data = await fetcher();
  
  // Store in cache
  cache.set(key, data, ttl);
  
  return data;
};

// Example usage
const fetchUser = async (id) => {
  return fetchWithCache(
    `user:${id}`,
    () => fetch(`/api/users/${id}`).then(res => res.json()),
    10 * 60 * 1000 // 10 minutes
  );
};

React Implementation with Cache Hooks

jsx
import { useAtom } from '@nexus-state/react';
import { useEffect, useState } from 'react';

// Custom hook for cached data
const useCachedData = (key, fetcher, ttl = 5 * 60 * 1000) => {
  const [cache] = useAtom(cacheAtom);
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      setError(null);
      
      try {
        const result = await fetchWithCache(key, fetcher, ttl);
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [key]);
  
  return { data, loading, error };
};

// Component using cached data
function UserComponent({ userId }) {
  const { data: user, loading, error } = useCachedData(
    `user:${userId}`,
    () => fetch(`/api/users/${userId}`).then(res => res.json()),
    10 * 60 * 1000 // 10 minutes
  );
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!user) return <div>No user found</div>;
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Advanced Caching Patterns

Cache with Persistence

javascript
import { atom, createStore } from '@nexus-state/core';
import { createPersist } from '@nexus-state/persist';

// Cache that persists to localStorage
class PersistentCache {
  constructor(storageKey = 'nexus-cache') {
    this.storageKey = storageKey;
    this.cache = new Map();
    this.timeouts = new Map();
    this.loadFromStorage();
  }
  
  loadFromStorage() {
    try {
      const stored = localStorage.getItem(this.storageKey);
      if (stored) {
        const parsed = JSON.parse(stored);
        const now = Date.now();
        
        // Load non-expired entries
        for (const [key, { value, expires }] of Object.entries(parsed)) {
          if (expires > now) {
            this.cache.set(key, value);
            
            // Set timeout for expiration
            const timeout = setTimeout(() => {
              this.cache.delete(key);
              this.saveToStorage();
            }, expires - now);
            
            this.timeouts.set(key, timeout);
          }
        }
      }
    } catch (error) {
      console.warn('Failed to load cache from storage:', error);
    }
  }
  
  saveToStorage() {
    try {
      const toStore = {};
      const now = Date.now();
      
      for (const [key, value] of this.cache.entries()) {
        // Get expiration time from timeout
        const timeout = this.timeouts.get(key);
        if (timeout) {
          const expires = now + (timeout._idleTimeout || 0);
          toStore[key] = { value, expires };
        }
      }
      
      localStorage.setItem(this.storageKey, JSON.stringify(toStore));
    } catch (error) {
      console.warn('Failed to save cache to storage:', error);
    }
  }
  
  get(key) {
    return this.cache.get(key);
  }
  
  set(key, value, ttl = 5 * 60 * 1000) {
    this.cache.set(key, value);
    
    // Clear existing timeout
    if (this.timeouts.has(key)) {
      clearTimeout(this.timeouts.get(key));
    }
    
    // Set expiration timeout
    const timeout = setTimeout(() => {
      this.cache.delete(key);
      this.timeouts.delete(key);
      this.saveToStorage();
    }, ttl);
    
    this.timeouts.set(key, timeout);
    this.saveToStorage();
  }
  
  has(key) {
    return this.cache.has(key);
  }
  
  delete(key) {
    this.cache.delete(key);
    
    if (this.timeouts.has(key)) {
      clearTimeout(this.timeouts.get(key));
      this.timeouts.delete(key);
    }
    
    this.saveToStorage();
  }
  
  clear() {
    this.cache.clear();
    
    for (const timeout of this.timeouts.values()) {
      clearTimeout(timeout);
    }
    this.timeouts.clear();
    
    this.saveToStorage();
  }
}

const persistentCache = new PersistentCache();
const persistentCacheAtom = atom(persistentCache);

const store = createStore();
store.use(createPersist({
  key: 'app-cache',
  storage: localStorage,
  atoms: [persistentCacheAtom]
}));