Skip to content

Pattern Composition

For complex applications, compose core and advanced patterns for maximum flexibility and separation of concerns.

Overview

The Context-Action framework provides core and advanced patterns that can be composed together:

Core Patterns:

  • 🎯 Action Only Pattern: Pure action dispatching without stores
  • 🏪 Store Only Pattern: State management without actions

Advanced Patterns:

  • 🔧 Ref Context Pattern: Direct DOM manipulation with zero re-renders

Complete Composition Example

tsx
// 1. Create separate contexts with renaming patterns
const { 
  Provider: EventActionProvider, 
  useActionDispatch: useEventAction,
  useActionHandler: useEventActionHandler
} = createActionContext<EventActions>('Events');

const {
  Provider: AppStoreProvider,
  useStore: useAppStore,
  useStoreManager: useAppStoreManager
} = createStoreContext('App', {
  user: { id: '', name: '' },
  counter: 0
});

const {
  Provider: UIRefsProvider,
  useRefHandler: useUIRef
} = createRefContext<{
  cursor: HTMLDivElement;
  notification: HTMLDivElement;
  modal: HTMLDivElement;
}>('UIRefs');

// 2. Compose core and advanced patterns
function App() {
  return (
    <EventActionProvider>
      <AppStoreProvider>
        <UIRefsProvider>
          <ComplexComponent />
        </UIRefsProvider>
      </AppStoreProvider>
    </EventActionProvider>
  );
}

// 3. Use core and advanced patterns in components
function ComplexComponent() {
  // Actions from Action Only pattern
  const dispatch = useEventAction();
  
  // State from Store Only pattern
  const userStore = useAppStore('user');
  const counterStore = useAppStore('counter');
  
  // Refs from RefContext pattern
  const cursor = useUIRef('cursor');
  const notification = useUIRef('notification');
  
  const user = useStoreValue(userStore);
  const counter = useStoreValue(counterStore);
  
  // Action handlers that update state and manipulate DOM (properly memoized)
  const updateUserHandler = useCallback((payload) => {
    // Update store
    userStore.setValue(payload);
    
    // Show notification with direct DOM manipulation
    if (notification.target) {
      notification.target.textContent = `User ${payload.name} updated!`;
      notification.target.style.display = 'block';
      notification.target.style.opacity = '1';
      
      setTimeout(() => {
        if (notification.target) {
          notification.target.style.opacity = '0';
          setTimeout(() => {
            if (notification.target) {
              notification.target.style.display = 'none';
            }
          }, 300);
        }
      }, 2000);
    }
    
    // Dispatch analytics
    dispatch('analytics', { event: 'user-updated' });
  }, [userStore, notification, dispatch]);

  useEventActionHandler('updateUser', updateUserHandler);
  
  // Mouse events with direct DOM manipulation
  const handleMouseMove = useCallback((e: React.MouseEvent) => {
    if (cursor.target) {
      cursor.target.style.transform = `translate3d(${e.clientX}px, ${e.clientY}px, 0)`;
    }
  }, [cursor]);
  
  return (
    <div onMouseMove={handleMouseMove}>
      <div>User: {user.name}</div>
      <div>Counter: {counter}</div>
      
      {/* Direct DOM manipulation elements */}
      <div
        ref={cursor.setRef}
        className="fixed w-4 h-4 bg-blue-500 rounded-full pointer-events-none z-50"
        style={{ transform: 'translate3d(0, 0, 0)' }}
      />
      
      <div
        ref={notification.setRef}
        className="fixed top-4 right-4 bg-green-500 text-white p-4 rounded shadow-lg"
        style={{ display: 'none', opacity: 0, transition: 'opacity 300ms' }}
      />
    </div>
  );
}

Domain-Based Composition

Business + UI Domain Separation

tsx
// Business domain
const { 
  Provider: UserBusinessProvider, 
  useStore: useUserBusinessStore,
  useActionDispatch: useUserBusinessAction,
  useActionHandler: useUserBusinessActionHandler
} = createBusinessDomain();

// UI domain  
const {
  Provider: UserUIProvider,
  useStore: useUserUIStore,
  useRefHandler: useUserUIRef
} = createUIDomain();

// Combined provider
function UserProvider({ children }) {
  return (
    <UserBusinessProvider>
      <UserUIProvider>
        {children}
      </UserUIProvider>
    </UserBusinessProvider>
  );
}

Domain-Specific Logic Hooks

tsx
// Combine business and UI logic in reusable hooks
export function useUserEditor() {
  // Business layer
  const profileStore = useUserBusinessStore('profile');
  const businessAction = useUserBusinessAction();
  
  // UI layer  
  const viewStore = useUserUIStore('view');
  const modalRef = useUserUIRef('modal');
  
  const profile = useStoreValue(profileStore);
  const view = useStoreValue(viewStore);
  
  const startEditing = useCallback(() => {
    viewStore.setValue({ ...view, isEditing: true });
    
    // Show modal with direct DOM manipulation
    if (modalRef.target) {
      modalRef.target.style.display = 'block';
      modalRef.target.style.opacity = '1';
    }
  }, [viewStore, view, modalRef]);
  
  const saveChanges = useCallback(async (data) => {
    await businessAction('updateProfile', { data });
    viewStore.setValue({ ...view, isEditing: false });
    
    // Hide modal
    if (modalRef.target) {
      modalRef.target.style.opacity = '0';
      setTimeout(() => {
        if (modalRef.target) {
          modalRef.target.style.display = 'none';
        }
      }, 300);
    }
  }, [businessAction, viewStore, view, modalRef]);
  
  return {
    profile,
    isEditing: view.isEditing,
    startEditing,
    saveChanges
  };
}

Architecture Patterns

MVVM Architecture Integration

tsx
// Model Layer (Store Only Pattern)
const ModelLayer = createStoreContext('Model', {
  userData: { id: '', name: '', email: '' },
  appState: { loading: false, error: null }
});

// ViewModel Layer (Action Only Pattern) 
const ViewModelLayer = createActionContext<{
  loadUser: { userId: string };
  updateUser: { data: Partial<UserData> };
  showError: { message: string };
}>('ViewModel');

// Performance Layer (RefContext Pattern)
const PerformanceLayer = createRefContext<{
  loadingSpinner: HTMLDivElement;
  errorModal: HTMLDivElement;
  userCard: HTMLDivElement;
}>('Performance');

// View Layer (React Components)
function UserView() {
  // Model
  const userStore = ModelLayer.useStore('userData');
  const user = useStoreValue(userStore);
  
  // ViewModel  
  const dispatch = ViewModelLayer.useActionDispatch();
  
  // Performance
  const spinnerRef = PerformanceLayer.useRefHandler('loadingSpinner');
  
  // Business logic handlers
  ViewModelLayer.useActionHandler('loadUser', async (payload) => {
    // Show loading spinner directly
    if (spinnerRef.target) {
      spinnerRef.target.style.display = 'block';
    }
    
    try {
      const userData = await userAPI.load(payload.userId);
      userStore.setValue(userData);
    } finally {
      // Hide loading spinner
      if (spinnerRef.target) {
        spinnerRef.target.style.display = 'none';
      }
    }
  });
  
  return (
    <div>
      <div>User: {user.name}</div>
      <div ref={spinnerRef.setRef} style={{ display: 'none' }}>
        Loading...
      </div>
    </div>
  );
}

Performance Optimization

Layer-Specific Optimizations

tsx
// Performance Layer: Zero re-renders for animations
const AnimationLayer = createRefContext<{
  particles: HTMLDivElement;
  canvas: HTMLCanvasElement;
}>('Animation');

// State Layer: Selective updates for data
const DataLayer = createStoreContext('Data', {
  particleCount: 100,
  animationSpeed: 1.0,
  // Use reference equality for large datasets
  particleData: {
    initialValue: [] as Particle[],
    strategy: 'reference'
  }
});

// Action Layer: Coordination between layers
const CoordinationLayer = createActionContext<{
  updateAnimation: { speed: number };
  addParticle: { particle: Particle };
}>('Coordination');

function PerformanceOptimizedComponent() {
  // Data layer triggers React re-renders only for configuration
  const speedStore = DataLayer.useStore('animationSpeed');
  const speed = useStoreValue(speedStore);
  
  // Performance layer handles 60fps updates without React
  const particlesRef = AnimationLayer.useRefHandler('particles');
  
  // Coordination layer bridges between data and performance
  CoordinationLayer.useActionHandler('updateAnimation', (payload) => {
    speedStore.setValue(payload.speed); // Update config (React re-render)
    
    // Update animation directly (no React re-render)
    if (particlesRef.target) {
      particlesRef.target.style.animationDuration = `${1 / payload.speed}s`;
    }
  });
}

Best Practices

1. Handler Registration (Critical)

  • Always use useCallback: All action handlers must be wrapped with useCallback to prevent infinite re-registration
  • Proper Dependencies: Include only necessary dependencies in the dependency array
  • Avoid Inline Functions: Never pass inline functions directly to action handlers

Important: For detailed handler registration patterns, see the Handler Registration Guide

2. Pattern Selection Strategy

  • Start with Store Only for simple state management
  • Add Action Only when you need side effects or complex workflows
  • Add RefContext when you need high-performance DOM manipulation
  • Compose all patterns for full-featured applications

2. Domain Boundaries

  • Business Logic: Use Action Only + Store Only patterns
  • UI Interactions: Use Store Only + RefContext patterns
  • Performance Critical: Use RefContext pattern primarily
  • Cross-Domain: Use composition hooks for integration

3. Provider Organization

  • Nested Providers: Organize by domain hierarchy
  • HOC Pattern: Use Higher-Order Components for automatic wrapping
  • Conditional Providers: Load providers based on feature flags
  • Lazy Providers: Load providers on-demand for performance

4. Performance Considerations

  • RefContext: Use for animations, real-time interactions
  • Store Pattern: Use reference equality for large datasets
  • Action Pattern: Keep handlers lightweight, use async appropriately
  • Composition: Minimize cross-pattern dependencies

Migration Guide

For users upgrading from earlier versions, see the main Migration Guide for comprehensive upgrade instructions.

Pattern composition provides the ultimate flexibility while maintaining clear separation of concerns and optimal performance characteristics.

Released under the Apache-2.0 License.