Skip to content

Register Patterns

Handler registration patterns and advanced configuration options for the Context-Action framework.

Prerequisites

This pattern guide assumes you have a basic action context setup. If not, refer to Basic Action Setup first.

Import

typescript
import { 
  ActionRegister,
  ActionPayloadMap,
  ActionHandler,
  HandlerConfig
} from '@context-action/core';
import { 
  createActionContext,
  useActionHandler,
  useActionRegister
} from '@context-action/react';
import { useEffect, useRef, useState, useCallback } from 'react';

Setup Patterns

Basic Action Context Setup

typescript
// Define action types
interface AppActions extends ActionPayloadMap {
  updateUser: { id: string; name: string; email: string };
  deleteUser: { id: string };
  validateData: { data: any };
  saveData: { data: any };
  resetState: void;
}

// Create action context
const {
  Provider: AppActionProvider,
  useActionDispatch: useAppDispatch,
  useActionHandler: useAppHandler,
  useActionRegister: useAppRegister
} = createActionContext<AppActions>('App');

// Provider setup
function App() {
  return (
    <AppActionProvider>
      <AppContent />
    </AppActionProvider>
  );
}

Action Register Access

typescript
// Component with handler registration
function HandlerSetup() {
  const register = useAppRegister();
  
  // Register handlers using useActionHandler hook (recommended)
  useAppHandler('updateUser', async (payload, controller) => {
    const user = await userService.update(payload.id, payload);
    controller.setResult(user);
  });
  
  // Or register directly with the register instance
  useEffect(() => {
    const unregister = register.register('deleteUser', async (payload, controller) => {
      await userService.delete(payload.id);
      controller.setResult({ deleted: true });
    });
    
    return unregister; // Cleanup on unmount
  }, [register]);
  
  return <div>Handlers registered</div>;
}

Basic Handler Registration

Register action handlers with type safety and configuration options.

Simple Handler Registration

typescript
// Using useActionHandler hook (recommended)
function UserComponent() {
  // Direct handler registration with useActionHandler
  useAppHandler('updateUser', async (payload, controller) => {
    const user = await userService.update(payload.id, payload);
    controller.setResult(user);
  });
  
  return <div>User Management Component</div>;
}

Handler with Configuration

typescript
// Using direct register access for configuration options
function ConfiguredHandlerSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    // Handler with priority and tags
    const unregister = register.register('updateUser', async (payload, controller) => {
      const user = await userService.update(payload.id, payload);
      controller.setResult(user);
    }, {
      priority: 100,
      tags: ['user', 'crud']
    });
    
    return unregister;
  }, [register]);
  
  return <div>Configured handlers registered</div>;
}

Handler Configuration Options

Priority and Execution Order

typescript
function PriorityHandlerSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    // Higher priority handlers execute first
    const unregisterValidation = register.register('validateUser', async (payload, controller) => {
      const isValid = await userService.validate(payload);
      if (!isValid) {
        controller.abort('Validation failed');
        return;
      }
      controller.setResult({ validated: true });
    }, {
      priority: 100,  // High priority - executes first
      tags: ['validation']
    });
    
    const unregisterSave = register.register('saveUser', async (payload, controller) => {
      const user = await userService.save(payload);
      controller.setResult(user);
    }, {
      priority: 50,   // Lower priority - executes after validation
      tags: ['persistence']
    });
    
    return () => {
      unregisterValidation();
      unregisterSave();
    };
  }, [register]);
  
  return <div>Priority handlers registered</div>;
}

Performance Optimization

typescript
function PerformanceHandlerSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    // Debounce search input (wait for pause)
    const unregisterSearch = register.register('searchUsers', async (payload, controller) => {
      const results = await userService.search(payload.query);
      controller.setResult(results);
    }, {
      debounce: 300,  // Wait 300ms after last call
      id: 'searchUsersHandler'
    });
    
    // Throttle scroll events (limit frequency)
    const unregisterScroll = register.register('updateScrollPosition', async (payload, controller) => {
      // Update scroll position in store or analytics
      scrollService.updatePosition(payload.position);
      controller.setResult({ updated: true });
    }, {
      throttle: 100,  // Max once per 100ms
      id: 'updateScrollPositionHandler'
    });
    
    return () => {
      unregisterSearch();
      unregisterScroll();
    };
  }, [register]);
  
  return <div>Performance-optimized handlers registered</div>;
}

Conditional Handlers

typescript
function ConditionalHandlerSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    // Conditional handler based on user subscription
    const unregisterPremium = register.register('premiumFeature', async (payload, controller) => {
      const result = await premiumService.executePremiumFeature(payload);
      controller.setResult(result);
    }, {
      priority: 100,
      id: 'premiumFeatureHandler'
    });
    
    // Environment-specific handlers
    const unregisterDebug = register.register('debugAction', async (payload, controller) => {
      console.log('Debug action:', payload);
      debugService.log(payload);
      controller.setResult({ debugged: true });
    }, {
      priority: 50,
      id: 'debugActionHandler'
    });
    
    const unregisterAnalytics = register.register('analyticsTrack', async (payload, controller) => {
      await analyticsService.track(payload.event, payload.data);
      controller.setResult({ tracked: true });
    }, {
      priority: 80,
      id: 'analyticsTrackHandler'
    });
    
    return () => {
      unregisterPremium();
      unregisterDebug();
      unregisterAnalytics();
    };
  }, [register]);
  
  return <div>Conditional handlers registered</div>;
}

One-Time Handlers

typescript
function OneTimeHandlerSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    // Handler executes once then auto-removes
    const unregister = register.register('initializeApp', async (payload, controller) => {
      await appService.initialize();
      controller.setResult({ initialized: true });
    }, {
      once: true,
      priority: 1000,
      id: 'initializeAppHandler'
    });
    
    // No need to call unregister for one-time handlers
    // They auto-remove after execution
    return unregister;
  }, [register]);
  
  return <div>One-time handler registered</div>;
}

Advanced Configuration

Comprehensive Configuration

typescript
function FullConfigurationSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    const unregister = register.register('fullConfigHandler', async (payload, controller) => {
      // Core business logic implementation
      const result = await businessService.processData(payload);
      controller.setResult(result);
    }, {
      priority: 100,          // Execution priority
      id: 'fullConfigHandler', // Handler unique identifier
      blocking: true,         // Wait for async handlers to complete
      once: false,            // Can execute multiple times
      debounce: 200,          // Debounce calls (ms)
      throttle: 1000          // Throttle execution (ms)
    });
    
    return unregister;
  }, [register]);
  
  return <div>Fully configured handler registered</div>;
}

Handler Dependencies

typescript
function DependencyHandlerSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    // Handler that depends on other handlers
    const unregisterDependent = register.register('dependentHandler', async (payload, controller) => {
      // This handler runs after validationHandler and authHandler
      const processedData = await dataService.process(payload);
      controller.setResult(processedData);
    }, {
      priority: 50,
      id: 'dependentHandler',
      blocking: true
    });
    
    // Conflicting handlers (only one will execute)
    const unregisterModern = register.register('modernHandler', async (payload, controller) => {
      const result = await modernService.process(payload);
      controller.setResult(result);
    }, {
      priority: 100,
      id: 'modernHandler',
      blocking: false
    });
    
    return () => {
      unregisterDependent();
      unregisterModern();
    };
  }, [register]);
  
  return <div>Dependency handlers registered</div>;
}

Error Handling in Handlers

Graceful Error Recovery

typescript
function ErrorRecoveryHandlerSetup() {
  // Using useActionHandler hook for simple error recovery
  useAppHandler('resilientOperation', async (payload, controller) => {
    try {
      const result = await riskyOperation(payload);
      controller.setResult(result);
    } catch (error) {
      // Log error but don't abort pipeline
      console.error('Operation failed:', error);
      controller.setResult({ error: error.message, fallback: true });
    }
  });
  
  return <div>Resilient handler registered</div>;
}

Circuit Breaker Pattern

typescript
function CircuitBreakerHandlerSetup() {
  const register = useAppRegister();
  
  // Circuit breaker state (in real apps, use external state management)
  const circuitState = useRef({
    failureCount: 0,
    MAX_FAILURES: 3
  });
  
  useEffect(() => {
    const unregister = register.register('externalAPI', async (payload, controller) => {
      if (circuitState.current.failureCount >= circuitState.current.MAX_FAILURES) {
        controller.abort('Circuit breaker open');
        return;
      }
      
      try {
        const result = await externalAPI.call(payload);
        circuitState.current.failureCount = 0;  // Reset on success
        controller.setResult(result);
      } catch (error) {
        circuitState.current.failureCount++;
        throw error;
      }
    }, {
      tags: ['external', 'api'],
      timeout: 5000
    });
    
    return unregister;
  }, [register]);
  
  return <div>Circuit breaker handler registered</div>;
}

Validation Handlers

typescript
function ValidationHandlerSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    const unregister = register.register('validateInput', async (payload, controller) => {
      const errors = [];
      
      if (!payload.email?.includes('@')) {
        errors.push('Invalid email format');
      }
      
      if (!payload.name?.trim()) {
        errors.push('Name is required');
      }
      
      if (errors.length > 0) {
        controller.abort('Validation failed', { errors });
        return;
      }
      
      // Modify payload for subsequent handlers
      controller.modifyPayload(p => ({
        ...p,
        email: p.email.toLowerCase(),
        name: p.name.trim()
      }));
    }, {
      priority: 1000,  // Execute first
      tags: ['validation']
    });
    
    return unregister;
  }, [register]);
  
  return <div>Validation handler registered</div>;
}

Handler Lifecycle Management

Dynamic Handler Registration

typescript
function DynamicHandlerSetup() {
  const register = useAppRegister();
  const [userRole, setUserRole] = useState<string>('guest');
  
  // Register handlers dynamically based on conditions
  const registerUserHandlers = useCallback((role: string) => {
    const unregisterFunctions: (() => void)[] = [];
    
    if (role === 'admin') {
      const unregister = register.register('adminAction', async (payload, controller) => {
        const result = await adminService.executeAction(payload);
        controller.setResult(result);
      }, {
        tags: ['admin', 'privileged']
      });
      unregisterFunctions.push(unregister);
    }
    
    if (role === 'moderator') {
      const unregister = register.register('moderateContent', async (payload, controller) => {
        const result = await moderationService.moderate(payload);
        controller.setResult(result);
      }, {
        tags: ['moderation']
      });
      unregisterFunctions.push(unregister);
    }
    
    return () => {
      unregisterFunctions.forEach(fn => fn());
    };
  }, [register]);
  
  useEffect(() => {
    return registerUserHandlers(userRole);
  }, [userRole, registerUserHandlers]);
  
  return (
    <div>
      <div>Dynamic handlers for role: {userRole}</div>
      <button onClick={() => setUserRole('admin')}>Set Admin</button>
      <button onClick={() => setUserRole('moderator')}>Set Moderator</button>
    </div>
  );
}

Handler Cleanup

typescript
function HandlerCleanupSetup() {
  const register = useAppRegister();
  const unregisterFunctionsRef = useRef(new Set<() => void>());
  
  useEffect(() => {
    // Store unregister functions for cleanup tracking
    const unregisterFunctions = unregisterFunctionsRef.current;
    
    // Register with cleanup tracking
    const unregister = register.register('temporaryHandler', async (payload, controller) => {
      // Temporary handler logic
      const result = await tempService.process(payload);
      controller.setResult(result);
    }, {
      tags: ['temporary']
    });
    
    unregisterFunctions.add(unregister);
    
    // Cleanup function
    return () => {
      unregisterFunctions.forEach(fn => fn());
      unregisterFunctions.clear();
    };
  }, [register]);
  
  // Manual cleanup function (if needed)
  const cleanupAllHandlers = useCallback(() => {
    unregisterFunctionsRef.current.forEach(fn => fn());
    unregisterFunctionsRef.current.clear();
  }, []);
  
  return (
    <div>
      <div>Temporary handlers with cleanup tracking</div>
      <button onClick={cleanupAllHandlers}>Manual Cleanup</button>
    </div>
  );
}

Bulk Registration

typescript
function BulkRegistrationSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    // Define handlers configuration
    const handlersConfig = {
      validateUser: { 
        handler: async (payload: any, controller: any) => {
          const isValid = await userService.validate(payload);
          controller.setResult({ isValid });
        }, 
        priority: 100 
      },
      saveUser: { 
        handler: async (payload: any, controller: any) => {
          const user = await userService.save(payload);
          controller.setResult(user);
        }, 
        priority: 50 
      },
      notifyUser: { 
        handler: async (payload: any, controller: any) => {
          await notificationService.send(payload);
          controller.setResult({ notified: true });
        }, 
        priority: 10 
      }
    };
    
    // Register multiple handlers at once
    const unregisterFunctions = Object.entries(handlersConfig).map(([action, config]) => {
      return register.register(action as any, config.handler, {
        priority: config.priority,
        tags: ['user', 'bulk-registered']
      });
    });
    
    // Cleanup all registered handlers
    return () => {
      unregisterFunctions.forEach(fn => fn());
    };
  }, [register]);
  
  return <div>Bulk handlers registered</div>;
}

Metadata and Monitoring

Handler Metrics

typescript
function MetricsHandlerSetup() {
  const register = useAppRegister();
  
  useEffect(() => {
    const unregister = register.register('monitoredHandler', async (payload, controller) => {
      const result = await businessService.processWithMetrics(payload);
      controller.setResult(result);
    }, {
      metrics: {
        collectTiming: true,
        collectErrors: true,
        customMetrics: {
          businessMetric: (payload: any, result: any) => result.userCount || 0
        }
      },
      tags: ['monitored']
    });
    
    return unregister;
  }, [register]);
  
  return <div>Monitored handler registered</div>;
}

Registry Information

typescript
function RegistryInfoComponent() {
  const register = useAppRegister();
  const [registryInfo, setRegistryInfo] = useState<any>(null);
  
  const getRegistryStats = useCallback(() => {
    // Get registry statistics
    const info = register.getRegistryInfo();
    setRegistryInfo(info);
    console.log('Registry stats:', {
      totalActions: info.totalActions,
      totalHandlers: info.totalHandlers,
      registeredActions: info.registeredActions
    });
  }, [register]);
  
  useEffect(() => {
    getRegistryStats();
  }, [getRegistryStats]);
  
  return (
    <div>
      <h3>Registry Information</h3>
      {registryInfo && (
        <div>
          <p>Total Actions: {registryInfo.totalActions}</p>
          <p>Total Handlers: {registryInfo.totalHandlers}</p>
          <p>Registered Actions: {registryInfo.registeredActions?.join(', ')}</p>
        </div>
      )}
      <button onClick={getRegistryStats}>Refresh Stats</button>
    </div>
  );
}

Memory Management and Handler Limits

Configuring Handler Limits

The Context-Action framework includes memory management features to prevent excessive handler registration and potential memory leaks.

typescript
function MemoryManagedSetup() {
  // Configure ActionRegister with memory management
  const actionRegister = new ActionRegister<AppActions>({
    registry: {
      maxHandlersPerAction: 1000,    // Default: 1000 handlers per action
      debug: false                   // Disable debug for production
    }
  });
  
  return (
    <AppActionProvider actionRegister={actionRegister}>
      <AppContent />
    </AppActionProvider>
  );
}

Different Memory Strategies

typescript
function MemoryStrategies() {
  // Small applications - Conservative limits
  const smallAppRegister = new ActionRegister<AppActions>({
    registry: {
      maxHandlersPerAction: 100,     // Conservative limit
      debug: false
    }
  });
  
  // Enterprise applications - Higher limits
  const enterpriseRegister = new ActionRegister<AppActions>({
    registry: {
      maxHandlersPerAction: 10000,   // Higher limit for complex systems
      debug: false
    }
  });
  
  // Development/Testing - Unlimited (use with caution)
  const devRegister = new ActionRegister<AppActions>({
    registry: {
      maxHandlersPerAction: Infinity, // No limits - for controlled environments only
      debug: true
    }
  });
  
  return <div>Memory strategies configured</div>;
}

Handling Handler Limit Warnings

typescript
function HandlerLimitManagement() {
  const register = useAppRegister();
  const [handlerCount, setHandlerCount] = useState(0);
  
  const addHandler = useCallback(() => {
    const unregister = register.register('testAction', async (payload, controller) => {
      // Handler logic
      controller.setResult({ processed: true });
    }, {
      id: `handler-${Date.now()}` // Unique ID to prevent replacement
    });
    
    // Check current handler count
    const count = register.getHandlerCount('testAction');
    setHandlerCount(count);
    
    return unregister;
  }, [register]);
  
  const getStats = useCallback(() => {
    const stats = register.getActionStats('testAction');
    console.log('Handler stats:', {
      handlerCount: stats?.handlerCount || 0,
      maxLimit: 1000 // Default limit
    });
  }, [register]);
  
  return (
    <div>
      <p>Current handlers: {handlerCount}</p>
      <button onClick={addHandler}>Add Handler</button>
      <button onClick={getStats}>Check Stats</button>
    </div>
  );
}

Memory-Efficient Handler Patterns

typescript
function MemoryEfficientHandlers() {
  const register = useAppRegister();
  
  // Pattern 1: Use handler replacement instead of accumulation
  useEffect(() => {
    const unregister = register.register('userAction', async (payload, controller) => {
      // Handler logic
      controller.setResult(await userService.process(payload));
    }, {
      id: 'user-handler',
      replaceExisting: true  // Replace instead of adding new handlers
    });
    
    return unregister;
  }, [register]);
  
  // Pattern 2: Cleanup handlers when no longer needed
  const [isFeatureEnabled, setIsFeatureEnabled] = useState(false);
  
  useEffect(() => {
    if (!isFeatureEnabled) return;
    
    const unregister = register.register('featureAction', async (payload, controller) => {
      // Feature-specific logic
      controller.setResult(await featureService.execute(payload));
    }, {
      id: 'feature-handler'
    });
    
    return unregister; // Auto-cleanup when feature is disabled
  }, [register, isFeatureEnabled]);
  
  // Pattern 3: Use once handlers for initialization
  useEffect(() => {
    const unregister = register.register('initAction', async (payload, controller) => {
      await appInitService.initialize();
      controller.setResult({ initialized: true });
    }, {
      once: true, // Auto-removes after first execution
      id: 'init-handler'
    });
    
    return unregister;
  }, [register]);
  
  return (
    <div>
      <button onClick={() => setIsFeatureEnabled(!isFeatureEnabled)}>
        Toggle Feature: {isFeatureEnabled ? 'ON' : 'OFF'}
      </button>
    </div>
  );
}

Production Memory Management

typescript
function ProductionMemorySetup() {
  // Production-optimized configuration
  const productionRegister = new ActionRegister<AppActions>({
    name: 'ProductionApp',
    registry: {
      debug: false,                    // Disable debug logging
      maxHandlersPerAction: 500,       // Conservative limit for production
      autoCleanup: true,               // Enable automatic cleanup
      useConcurrencyQueue: true        // Enable thread-safe operations
    }
  });
  
  return (
    <AppActionProvider actionRegister={productionRegister}>
      <ProductionApp />
    </AppActionProvider>
  );
}

Memory Monitoring and Alerts

typescript
function MemoryMonitoring() {
  const register = useAppRegister();
  const [memoryStats, setMemoryStats] = useState<any>(null);
  
  const checkMemoryUsage = useCallback(() => {
    const registryInfo = register.getRegistryInfo();
    const stats = {
      totalHandlers: registryInfo.totalHandlers,
      totalActions: registryInfo.totalActions,
      averageHandlersPerAction: registryInfo.totalHandlers / Math.max(registryInfo.totalActions, 1),
      registeredActions: registryInfo.registeredActions
    };
    
    // Alert if memory usage is high
    if (stats.totalHandlers > 5000) {
      console.warn('High handler count detected:', stats.totalHandlers);
    }
    
    setMemoryStats(stats);
  }, [register]);
  
  // Monitor memory usage periodically
  useEffect(() => {
    const interval = setInterval(checkMemoryUsage, 30000); // Check every 30 seconds
    checkMemoryUsage(); // Initial check
    
    return () => clearInterval(interval);
  }, [checkMemoryUsage]);
  
  return (
    <div>
      <h4>Memory Usage Statistics</h4>
      {memoryStats && (
        <div>
          <p>Total Handlers: {memoryStats.totalHandlers}</p>
          <p>Total Actions: {memoryStats.totalActions}</p>
          <p>Avg Handlers/Action: {memoryStats.averageHandlersPerAction.toFixed(2)}</p>
          <p>Actions: {memoryStats.registeredActions.join(', ')}</p>
        </div>
      )}
      <button onClick={checkMemoryUsage}>Refresh Stats</button>
    </div>
  );
}

Use Case Guidelines

Application TypeRecommended LimitUse Case
Small Apps100-500Simple applications, limited features
Medium Apps1000 (default)Most standard applications
Large Apps5000-10000Enterprise applications, complex workflows
DevelopmentInfinityTesting environments only (not recommended for production)

Memory Best Practices

Recommended practices:

  • Use meaningful handler IDs and replaceExisting: true for predictable behavior
  • Clean up handlers when components unmount or features are disabled
  • Use once: true for initialization handlers
  • Monitor handler counts in production environments
  • Set appropriate limits based on your application's complexity

Anti-patterns to avoid:

  • Unlimited handlers (Infinity) in production environments
  • Accumulating handlers without cleanup
  • Missing handler IDs leading to handler duplication
  • Registering handlers in render loops
  • Ignoring memory limit warnings

Real-World Examples

Released under the Apache-2.0 License.