CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-recoil

Recoil is an experimental state management framework for React applications that provides atoms and selectors for fine-grained reactivity.

Pending
Overview
Eval results
Files

memory-management.mddocs/

Memory Management

Tools for managing memory usage and preventing unwanted garbage collection of Recoil state. The memory management system allows fine-grained control over when atoms and selectors are retained in memory versus cleaned up.

Capabilities

State Retention

Hook for preventing garbage collection of atoms, selectors, and retention zones.

/**
 * Retains Recoil state in memory, preventing garbage collection
 * until the component unmounts or dependencies change
 */
function useRetain(
  toRetain: RecoilValue<any> | RetentionZone | Array<RecoilValue<any> | RetentionZone>
): void;

Usage Examples:

import React from 'react';
import { useRetain, atom, selector, atomFamily } from 'recoil';

const expensiveDataState = selector({
  key: 'expensiveDataState',
  get: async () => {
    // Expensive computation or API call
    const response = await fetch('/api/expensive-data');
    return response.json();
  },
});

// Retain single state
function DataPreloader() {
  // Keep expensive data in memory even if no components are using it
  useRetain(expensiveDataState);
  
  return null; // This component just preloads data
}

// Retain multiple states
function CacheManager({ userIds }) {
  const userStates = userIds.map(id => userProfileState(id));
  
  // Keep all user profiles in memory
  useRetain(userStates);
  
  return <div>Caching {userIds.length} user profiles</div>;
}

// Conditional retention
function ConditionalCache({ shouldCache, dataState }) {
  // Only retain if shouldCache is true
  if (shouldCache) {
    useRetain(dataState);
  }
  
  return <div>Cache status: {shouldCache ? 'active' : 'inactive'}</div>;
}

// Retain family instances
function FamilyCache({ activeItems }) {
  const itemStates = activeItems.map(id => itemState(id));
  
  // Keep active items in memory for fast access
  useRetain(itemStates);
  
  return <div>Retaining {activeItems.length} items</div>;
}

Retention Zones

System for grouping related state for coordinated memory management.

/**
 * Creates a retention zone for coordinated memory management
 */
function retentionZone(): RetentionZone;

class RetentionZone {
  // Internal implementation details
}

Usage Examples:

import React, { useMemo } from 'react';
import { retentionZone, useRetain, atom, atomFamily } from 'recoil';

// Create retention zone for related data
function UserDataManager({ userId }) {
  const userZone = useMemo(() => retentionZone(), [userId]);
  
  // Retain the entire zone
  useRetain(userZone);
  
  // All user-related data will be retained together
  return (
    <div>
      <UserProfile userId={userId} retentionZone={userZone} />
      <UserPosts userId={userId} retentionZone={userZone} />
      <UserSettings userId={userId} retentionZone={userZone} />
    </div>
  );
}

// Atoms that belong to a retention zone
const userProfileState = atomFamily({
  key: 'userProfileState',
  default: null,
  effects: (userId) => [
    ({node}) => {
      // Associate with retention zone if available
      const zone = getCurrentRetentionZone(); // Custom context
      if (zone) {
        zone.retain(node);
      }
    },
  ],
});

// Page-level retention zone
function PageWithRetention({ page }) {
  const pageZone = useMemo(() => retentionZone(), [page]);
  
  // Retain all data related to this page
  useRetain(pageZone);
  
  return (
    <RetentionZoneProvider zone={pageZone}>
      <PageContent page={page} />
    </RetentionZoneProvider>
  );
}

// Custom hook for zone-aware state
function useZoneAwareState(stateFamily, param) {
  const zone = useRetentionZone(); // Custom hook
  const state = stateFamily(param);
  
  // Automatically retain state in current zone
  useRetain([state, zone]);
  
  return state;
}

Memory Management Patterns

Common patterns for effective memory management in Recoil applications.

Usage Examples:

import React, { useEffect, useMemo } from 'react';
import { useRetain, atomFamily, selectorFamily } from 'recoil';

// LRU-style retention for frequently accessed data
function useLRURetention(items, maxRetained = 10) {
  const [retainedItems, setRetainedItems] = React.useState([]);
  
  // Update retained items when accessed items change
  useEffect(() => {
    const newRetained = [...new Set([...items, ...retainedItems])]
      .slice(0, maxRetained);
    setRetainedItems(newRetained);
  }, [items, maxRetained]);
  
  // Retain the LRU items
  const statesToRetain = retainedItems.map(id => itemState(id));
  useRetain(statesToRetain);
  
  return retainedItems;
}

// Component using LRU retention
function ItemBrowser({ currentItems }) {
  const retainedItems = useLRURetention(currentItems, 20);
  
  return (
    <div>
      <div>Current items: {currentItems.length}</div>
      <div>Retained in memory: {retainedItems.length}</div>
      {currentItems.map(id => (
        <ItemCard key={id} itemId={id} />
      ))}
    </div>
  );
}

// Preloading with retention
function useDataPreloader(dataKeys) {
  const [preloadedKeys, setPreloadedKeys] = React.useState([]);
  
  // Preload data in the background
  useEffect(() => {
    const preloadTimer = setTimeout(() => {
      setPreloadedKeys(dataKeys);
    }, 100); // Small delay to not block initial render
    
    return () => clearTimeout(preloadTimer);
  }, [dataKeys]);
  
  // Retain preloaded data
  const preloadedStates = preloadedKeys.map(key => dataState(key));
  useRetain(preloadedStates);
  
  return preloadedKeys;
}

// Route-based retention
function RouteDataManager({ route, subRoutes }) {
  const routeZone = useMemo(() => retentionZone(), [route]);
  
  // Retain main route data
  useRetain([routeZone, routeDataState(route)]);
  
  // Preload and retain sub-route data
  const subRouteStates = subRoutes.map(sr => routeDataState(sr));
  useRetain(subRouteStates);
  
  return (
    <div>
      <div>Route: {route}</div>
      <div>Sub-routes preloaded: {subRoutes.length}</div>
    </div>
  );
}

// Session-based retention
function useSessionRetention() {
  const sessionZone = useMemo(() => retentionZone(), []);
  
  // Retain for entire session
  useRetain(sessionZone);
  
  // Auto-retain frequently accessed data
  const retainInSession = (state) => {
    useRetain([state, sessionZone]);
    return state;
  };
  
  return { retainInSession, sessionZone };
}

// Memory-conscious component
function MemoryEfficientList({ items, visibleRange }) {
  const { start, end } = visibleRange;
  const visibleItems = items.slice(start, end);
  
  // Only retain visible items plus a small buffer
  const bufferSize = 5;
  const retainStart = Math.max(0, start - bufferSize);
  const retainEnd = Math.min(items.length, end + bufferSize);
  const itemsToRetain = items.slice(retainStart, retainEnd);
  
  const retainedStates = itemsToRetain.map(id => itemState(id));
  useRetain(retainedStates);
  
  return (
    <div>
      <div>Visible: {visibleItems.length} items</div>
      <div>Retained: {itemsToRetain.length} items</div>
      {visibleItems.map(id => (
        <ItemRow key={id} itemId={id} />
      ))}
    </div>
  );
}

Best Practices

When to Use Retention:

  1. Expensive Computations: Retain selectors with costly calculations
  2. Frequently Accessed Data: Keep commonly used data in memory
  3. Navigation Preloading: Retain data for likely next pages/routes
  4. User Session Data: Keep user-specific data throughout session
  5. Master-Detail Views: Retain master data when viewing details

When NOT to Use Retention:

  1. One-time Data: Don't retain data that's only used once
  2. Large Datasets: Be cautious with memory usage for large data
  3. Dynamic Content: Don't retain rapidly changing data unnecessarily
  4. Mobile Applications: Be more conservative on memory-constrained devices

Usage Examples:

import React from 'react';
import { useRetain, useRecoilValue } from 'recoil';

// Good: Retain expensive computation used across app
function GlobalDataProvider() {
  useRetain(expensiveGlobalComputationState);
  return null;
}

// Good: Retain user session data
function UserSessionManager({ user }) {
  useRetain([
    userProfileState(user.id),
    userPreferencesState(user.id),
    userPermissionsState(user.id),
  ]);
  return null;
}

// Caution: Large data retention
function DataTableManager({ tableId }) {
  const tableSize = useRecoilValue(tableSizeState(tableId));
  
  // Only retain if table is reasonably sized
  if (tableSize < 10000) {
    useRetain(tableDataState(tableId));
  }
  
  return <div>Table size: {tableSize} rows</div>;
}

// Good: Conditional retention based on usage patterns
function SmartRetention({ userId, isFrequentUser }) {
  const userDataState = userProfileState(userId);
  
  // Only retain for frequent users
  if (isFrequentUser) {
    useRetain(userDataState);
  }
  
  const userData = useRecoilValue(userDataState);
  return <div>User: {userData.name}</div>;
}

Performance Considerations

Memory Usage:

  • Monitor memory consumption in development tools
  • Use retention zones to group related state for easier management
  • Consider implementing custom retention policies for large applications

Cleanup:

  • Retention automatically cleans up when components unmount
  • Be mindful of component lifecycle when using retention
  • Use retention zones for coordinated cleanup of related state

Testing:

  • Test memory usage patterns in realistic scenarios
  • Verify that retention doesn't cause memory leaks
  • Monitor performance impact of retention policies

Install with Tessl CLI

npx tessl i tessl/npm-recoil

docs

advanced-hooks.md

concurrency-helpers.md

core-hooks.md

family-patterns.md

index.md

loadable-system.md

memory-management.md

root-provider.md

state-definition.md

tile.json