Skip to content

Memory Management

Patterns for preventing memory leaks and managing resources efficiently in store operations, including event handling, subscriptions, and data caching.

Event Object Storage Prevention

Never store DOM event objects directly in stores as they can cause memory leaks:

tsx
// ❌ WRONG: Storing event objects causes memory leaks
function BadEventHandler() {
  const eventStore = useAppStore('events');
  
  const handleClick = (event) => {
    // This will cause memory leaks - DOM events retain references
    eventStore.setValue({ lastEvent: event }); // Don't do this!
  };
  
  return <button onClick={handleClick}>Bad Click</button>;
}

// ✅ CORRECT: Extract only needed data from events
function GoodEventHandler() {
  const eventStore = useAppStore('events');
  
  const handleClick = useCallback((event) => {
    // Extract only the data you need
    const eventData = {
      clientX: event.clientX,
      clientY: event.clientY,
      target: event.target?.tagName,
      timestamp: Date.now(),
      type: event.type
    };
    
    eventStore.setValue({ lastEventData: eventData });
  }, [eventStore]);
  
  return <button onClick={handleClick}>Good Click</button>;
}

Cleanup Subscriptions

Always clean up manual subscriptions to prevent memory leaks:

tsx
function UserComponent() {
  const userStore = useAppStore('user');
  
  useEffect(() => {
    // Manual subscription with cleanup
    const unsubscribe = userStore.subscribe((user, prevUser) => {
      // Handle user changes
      console.log('User changed:', { user, prevUser });
    });
    
    // Cleanup subscription
    return unsubscribe;
  }, [userStore]);
  
  return <div>User Component</div>;
}

Cross-Platform Timeout Handling

Handle timeouts safely across different JavaScript environments:

tsx
// ✅ CORRECT: Cross-platform compatible timeout handling
function useCrossPlatformTimeout() {
  const [timeoutId, setTimeoutId] = useState<ReturnType<typeof requestAnimationFrame> | null>(null);
  
  const scheduleUpdate = useCallback((callback: () => void, delay: number = 0) => {
    // Cancel existing timeout
    if (timeoutId) {
      if (typeof timeoutId === 'number' && typeof window !== 'undefined') {
        cancelAnimationFrame(timeoutId);
      } else {
        clearTimeout(timeoutId);
      }
    }
    
    // Schedule new update - use requestAnimationFrame for better performance
    const newTimeoutId = delay === 0 
      ? requestAnimationFrame(callback)
      : setTimeout(callback, delay) as any;
    
    setTimeoutId(newTimeoutId);
  }, [timeoutId]);
  
  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (timeoutId) {
        if (typeof timeoutId === 'number' && typeof window !== 'undefined') {
          cancelAnimationFrame(timeoutId);
        } else {
          clearTimeout(timeoutId);
        }
      }
    };
  }, [timeoutId]);
  
  return scheduleUpdate;
}

Weak References for Large Data

Use WeakMap and WeakSet for caching large objects without preventing garbage collection:

tsx
const largeDataCache = new WeakMap();

const processedLargeData = useComputedStore(
  [largeDataStore],
  ([data]) => {
    // Use WeakMap for caching large objects
    if (largeDataCache.has(data)) {
      return largeDataCache.get(data);
    }
    
    const processed = expensiveProcessing(data);
    largeDataCache.set(data, processed);
    return processed;
  }
);

Memory Management Best Practices

Event Handling

  • Extract Data: Only extract needed data from DOM events
  • Avoid References: Never store entire DOM elements or React synthetic events
  • Use Primitives: Prefer primitive values and plain objects
  • Clean Extraction: Create new objects with only necessary properties

Subscription Management

  • Auto Cleanup: Use React hooks that handle cleanup automatically
  • Manual Cleanup: Always return cleanup functions from useEffect
  • Conditional Subscriptions: Unsubscribe when subscriptions are no longer needed
  • Memory Monitoring: Monitor subscription count in development

Data Caching

  • WeakMap/WeakSet: Use for object-keyed caches to allow garbage collection
  • TTL Patterns: Implement time-to-live for cache entries
  • Size Limits: Set maximum cache sizes and implement eviction policies
  • Cleanup Cycles: Periodically clean up stale cache entries

Common Memory Leak Sources

❌ Avoid These Patterns

  • Storing DOM events or synthetic events in stores
  • Creating closures that capture large objects unnecessarily
  • Forgetting to unsubscribe from manual subscriptions
  • Keeping references to unmounted components
  • Accumulating data without cleanup strategies

✅ Use These Patterns Instead

  • Extract only needed data from events before storing
  • Use React hooks with proper cleanup
  • Implement automatic subscription management
  • Use WeakMap/WeakSet for object references
  • Implement data retention policies

Released under the Apache-2.0 License.