Recoil is an experimental state management framework for React applications that provides atoms and selectors for fine-grained reactivity.
—
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.
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>;
}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;
}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>
);
}When to Use Retention:
When NOT to Use Retention:
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>;
}Memory Usage:
Cleanup:
Testing:
Install with Tessl CLI
npx tessl i tessl/npm-recoil