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

advanced-hooks.mddocs/

Advanced Hooks

Powerful hooks for complex operations including callbacks, transactions, snapshots, and state introspection. These hooks provide advanced capabilities for sophisticated state management patterns.

Capabilities

Recoil Callbacks

Hook for accessing Recoil state in event handlers and other non-render contexts.

/**
 * Returns a function that will run the callback that was passed when
 * calling this hook. Useful for accessing Recoil state in response to
 * events.
 */
function useRecoilCallback<Args extends ReadonlyArray<unknown>, Return>(
  fn: (interface: CallbackInterface) => (...args: Args) => Return,
  deps?: ReadonlyArray<unknown>
): (...args: Args) => Return;

interface CallbackInterface {
  /** Set the value of writeable Recoil state */
  set: <T>(recoilVal: RecoilState<T>, valOrUpdater: ((currVal: T) => T) | T) => void;
  /** Reset Recoil state to its default value */
  reset: (recoilVal: RecoilState<any>) => void;
  /** Force refresh a selector by clearing its cache */
  refresh: (recoilValue: RecoilValue<any>) => void;
  /** Current snapshot of all Recoil state */
  snapshot: Snapshot;
  /** Update state to match the provided snapshot */
  gotoSnapshot: (snapshot: Snapshot) => void;
  /** Execute an atomic transaction */
  transact_UNSTABLE: (cb: (i: TransactionInterface_UNSTABLE) => void) => void;
}

Usage Examples:

import React from 'react';
import { useRecoilCallback, atom } from 'recoil';

const userState = atom({ key: 'userState', default: null });
const loadingState = atom({ key: 'loadingState', default: false });

// Event handler that updates multiple states
function useAuthActions() {
  const login = useRecoilCallback(({set}) => async (credentials) => {
    set(loadingState, true);
    
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
      });
      const user = await response.json();
      set(userState, user);
    } catch (error) {
      console.error('Login failed:', error);
    } finally {
      set(loadingState, false);
    }
  }, []);
  
  const logout = useRecoilCallback(({reset, set}) => () => {
    reset(userState);
    set(loadingState, false);
  }, []);
  
  return { login, logout };
}

// Batch operations
function useBatchOperations() {
  const updateMultipleItems = useRecoilCallback(({set}) => (updates) => {
    // All updates happen in a single transaction
    updates.forEach(({id, data}) => {
      set(itemState(id), data);
    });
  }, []);
  
  return { updateMultipleItems };
}

// Reading state in callbacks
function useDataSync() {
  const syncData = useRecoilCallback(({snapshot}) => async () => {
    const currentUser = await snapshot.getPromise(userState);
    const currentData = await snapshot.getPromise(dataState);
    
    await fetch('/api/sync', {
      method: 'POST',
      body: JSON.stringify({ user: currentUser, data: currentData }),
    });
  }, []);
  
  return { syncData };
}

Transactions (Unstable)

Hook for executing atomic state updates that are guaranteed to be consistent.

/**
 * Returns a function that executes an atomic transaction for updating Recoil state
 */
function useRecoilTransaction_UNSTABLE<Args extends ReadonlyArray<unknown>>(
  fn: (interface: TransactionInterface_UNSTABLE) => (...args: Args) => void,
  deps?: ReadonlyArray<unknown>
): (...args: Args) => void;

interface TransactionInterface_UNSTABLE {
  /** Get the current value of Recoil state within the transaction */
  get<T>(a: RecoilValue<T>): T;
  /** Set the value of writeable Recoil state within the transaction */
  set<T>(s: RecoilState<T>, u: ((currVal: T) => T) | T): void;
  /** Reset Recoil state to its default value within the transaction */
  reset(s: RecoilState<any>): void;
}

Usage Examples:

import React from 'react';
import { useRecoilTransaction_UNSTABLE } from 'recoil';

// Atomic transfer between accounts
function useAccountOperations() {
  const transferFunds = useRecoilTransaction_UNSTABLE(({get, set}) => 
    (fromAccountId, toAccountId, amount) => {
      const fromBalance = get(accountBalanceState(fromAccountId));
      const toBalance = get(accountBalanceState(toAccountId));
      
      if (fromBalance < amount) {
        throw new Error('Insufficient funds');
      }
      
      // Both updates happen atomically
      set(accountBalanceState(fromAccountId), fromBalance - amount);
      set(accountBalanceState(toAccountId), toBalance + amount);
      
      // Log the transaction
      set(transactionLogState, (log) => [...log, {
        type: 'transfer',
        from: fromAccountId,
        to: toAccountId,
        amount,
        timestamp: Date.now(),
      }]);
    }, []);
  
  return { transferFunds };
}

// Complex state migration
function useStateMigration() {
  const migrateToNewFormat = useRecoilTransaction_UNSTABLE(({get, set, reset}) => () => {
    const oldData = get(legacyDataState);
    const users = get(usersState);
    
    // Reset old state
    reset(legacyDataState);
    
    // Transform and set new state
    const transformedData = oldData.map(item => ({
      id: item.legacyId,
      userId: users.find(u => u.email === item.userEmail)?.id,
      data: transformLegacyFormat(item.data),
      migrated: true,
    }));
    
    set(newDataState, transformedData);
    set(migrationStatusState, { completed: true, timestamp: Date.now() });
  }, []);
  
  return { migrateToNewFormat };
}

Snapshot Management

Hooks for working with immutable snapshots of Recoil state.

/**
 * Returns a snapshot of the current Recoil state and subscribes the component
 * to re-render when any state is updated
 */
function useRecoilSnapshot(): Snapshot;

/**
 * Updates Recoil state to match the provided snapshot
 */
function useGotoRecoilSnapshot(): (snapshot: Snapshot) => void;

/**
 * Observe all state transactions
 */
function useRecoilTransactionObserver_UNSTABLE(
  callback: (opts: {
    snapshot: Snapshot;
    previousSnapshot: Snapshot;
  }) => void
): void;

Usage Examples:

import React, { useState } from 'react';
import { 
  useRecoilSnapshot, 
  useGotoRecoilSnapshot,
  useRecoilTransactionObserver_UNSTABLE 
} from 'recoil';

// Undo/Redo functionality
function useUndoRedo() {
  const [history, setHistory] = useState([]);
  const [historyIndex, setHistoryIndex] = useState(-1);
  const snapshot = useRecoilSnapshot();
  const gotoSnapshot = useGotoRecoilSnapshot();
  
  // Save current state to history
  const saveState = () => {
    setHistory(prev => [...prev.slice(0, historyIndex + 1), snapshot]);
    setHistoryIndex(prev => prev + 1);
  };
  
  const undo = () => {
    if (historyIndex > 0) {
      const previousSnapshot = history[historyIndex - 1];
      gotoSnapshot(previousSnapshot);
      setHistoryIndex(prev => prev - 1);
    }
  };
  
  const redo = () => {
    if (historyIndex < history.length - 1) {
      const nextSnapshot = history[historyIndex + 1];
      gotoSnapshot(nextSnapshot);
      setHistoryIndex(prev => prev + 1);
    }
  };
  
  return {
    saveState,
    undo,
    redo,
    canUndo: historyIndex > 0,
    canRedo: historyIndex < history.length - 1,
  };
}

// State debugging component
function StateDebugger() {
  const snapshot = useRecoilSnapshot();
  const [selectedNode, setSelectedNode] = useState(null);
  
  const nodes = Array.from(snapshot.getNodes_UNSTABLE());
  
  return (
    <div>
      <h3>Recoil State Debugger</h3>
      <div style={{ display: 'flex' }}>
        <div style={{ width: '50%' }}>
          <h4>Atoms & Selectors</h4>
          {nodes.map(node => (
            <div 
              key={node.key}
              onClick={() => setSelectedNode(node)}
              style={{ 
                cursor: 'pointer',
                padding: '4px',
                backgroundColor: selectedNode === node ? '#eee' : 'transparent'
              }}
            >
              {node.key}
            </div>
          ))}
        </div>
        
        <div style={{ width: '50%' }}>
          {selectedNode && (
            <div>
              <h4>{selectedNode.key}</h4>
              <pre>
                {JSON.stringify(
                  snapshot.getLoadable(selectedNode).contents,
                  null,
                  2
                )}
              </pre>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// Transaction logger
function TransactionLogger() {
  const [transactions, setTransactions] = useState([]);
  
  useRecoilTransactionObserver_UNSTABLE(({snapshot, previousSnapshot}) => {
    const changedNodes = [];
    
    for (const node of snapshot.getNodes_UNSTABLE()) {
      const currentLoadable = snapshot.getLoadable(node);
      const previousLoadable = previousSnapshot.getLoadable(node);
      
      if (currentLoadable.state === 'hasValue' && 
          previousLoadable.state === 'hasValue' &&
          currentLoadable.contents !== previousLoadable.contents) {
        changedNodes.push({
          key: node.key,
          from: previousLoadable.contents,
          to: currentLoadable.contents,
        });
      }
    }
    
    if (changedNodes.length > 0) {
      setTransactions(prev => [...prev, {
        timestamp: Date.now(),
        changes: changedNodes,
      }]);
    }
  });
  
  return (
    <div>
      <h3>Transaction Log</h3>
      {transactions.map((transaction, i) => (
        <div key={i}>
          <strong>{new Date(transaction.timestamp).toLocaleTimeString()}</strong>
          {transaction.changes.map((change, j) => (
            <div key={j} style={{ marginLeft: '20px' }}>
              {change.key}: {JSON.stringify(change.from)} → {JSON.stringify(change.to)}
            </div>
          ))}
        </div>
      ))}
    </div>
  );
}

Snapshot Factory

Factory function for creating snapshots with initialized state.

/**
 * Factory to produce a Recoil snapshot object with all atoms in the default state
 */
function snapshot_UNSTABLE(initializeState?: (mutableSnapshot: MutableSnapshot) => void): Snapshot;

Usage Examples:

import { snapshot_UNSTABLE, atom } from 'recoil';

const userState = atom({ key: 'userState', default: null });
const settingsState = atom({ key: 'settingsState', default: {} });

// Create snapshot with specific initial state
function createTestSnapshot() {
  return snapshot_UNSTABLE(({set}) => {
    set(userState, { id: 1, name: 'Test User' });
    set(settingsState, { theme: 'dark', notifications: true });
  });
}

// Use in testing
function TestComponent() {
  const testSnapshot = createTestSnapshot();
  const gotoSnapshot = useGotoRecoilSnapshot();
  
  return (
    <button onClick={() => gotoSnapshot(testSnapshot)}>
      Load Test Data
    </button>
  );
}

// Export/Import state
function useStateExportImport() {
  const snapshot = useRecoilSnapshot();
  const gotoSnapshot = useGotoRecoilSnapshot();
  
  const exportState = async () => {
    const state = {};
    for (const node of snapshot.getNodes_UNSTABLE()) {
      const loadable = snapshot.getLoadable(node);
      if (loadable.state === 'hasValue') {
        state[node.key] = loadable.contents;
      }
    }
    return JSON.stringify(state);
  };
  
  const importState = (stateJson) => {
    const state = JSON.parse(stateJson);
    const importedSnapshot = snapshot_UNSTABLE(({set}) => {
      Object.entries(state).forEach(([key, value]) => {
        // Find the atom/selector by key and set its value
        const node = Array.from(snapshot.getNodes_UNSTABLE())
          .find(n => n.key === key);
        if (node) {
          set(node, value);
        }
      });
    });
    gotoSnapshot(importedSnapshot);
  };
  
  return { exportState, importState };
}

Advanced Patterns

State Coordination:

  • Use callbacks for complex event handling that affects multiple atoms
  • Use transactions for atomic updates that must be consistent
  • Use snapshots for undo/redo, testing, and state persistence

Performance Optimization:

  • Callbacks don't cause re-renders when dependencies change
  • Transactions batch updates to minimize re-renders
  • Snapshots are immutable and can be safely shared

Debugging and Development:

  • Transaction observers for logging state changes
  • Snapshot inspection for debugging complex state
  • State export/import for development and testing workflows

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