Context-Action Store Integration Architecture
1. Overview & Core Concepts
What is Context-Action Architecture?
The Context-Action framework is a revolutionary state management system designed to overcome the fundamental limitations of existing libraries through document-centric context separation and effective artifact management.
Project Philosophy
The Context-Action framework addresses critical issues in modern state management:
Problems with Existing Libraries:
- High React Coupling: Tight integration makes component modularization and props handling difficult
- Binary State Approach: Simple global/local state dichotomy fails to handle specific scope-based separation
- Inadequate Handler/Trigger Management: Poor support for complex interactions and business logic processing
Context-Action's Solution:
- Document-Artifact Centered Design: Context separation based on document themes and deliverable management
- Perfect Separation of Concerns:
- View design in isolation → Design Context
- Development architecture in isolation → Architecture Context
- Business logic in isolation → Business Context
- Data validation in isolation → Validation Context
- Clear Boundaries: Implementation results maintain distinct, well-defined domain boundaries
- Effective Document-Artifact Management: State management library that actively supports the relationship between documentation and deliverables
Architecture Implementation
The framework implements a clean separation of concerns through an MVVM-inspired pattern with three core patterns for complete domain isolation:
- Actions handle business logic and coordination (ViewModel layer) via
createActionContext
- Declarative Store Pattern manages state with domain isolation (Model layer) via
createDeclarativeStorePattern
- RefContext provides direct DOM manipulation with zero re-renders (Performance layer) via
createRefContext
- Components render UI (View layer)
- Context Boundaries isolate functional domains
- Type-Safe Integration through domain-specific hooks
Core Architecture Flow
[Component] → dispatch → [Action Pipeline] → handlers → [Store] → subscribe → [Component]
Context Separation Strategy
Domain-Based Context Architecture
- Business Context: Business logic, data processing, and domain rules (Actions + Stores)
- UI Context: Screen state, user interactions, and component behavior (Stores + RefContext)
- Performance Context: High-performance DOM manipulation and animations (RefContext)
- Validation Context: Data validation, form processing, and error handling (Actions + Stores)
- Design Context: Theme management, styling, layout, and visual states (Stores + RefContext)
- Architecture Context: System configuration, infrastructure, and technical decisions (Actions + Stores)
Document-Based Context Design
Each context is designed to manage its corresponding documentation and deliverables:
- Design Documentation → Design Context (themes, component specifications, style guides) → Stores + RefContext
- Business Requirements → Business Context (workflows, rules, domain logic) → Actions + Stores
- Performance Specifications → Performance Context (animations, interactions) → RefContext
- Architecture Documents → Architecture Context (system design, technical decisions) → Actions + Stores
- Validation Specifications → Validation Context (rules, schemas, error handling) → Actions + Stores
- UI Specifications → UI Context (interactions, state management, user flows) → All three patterns
Advanced Handler & Trigger Management
Context-Action provides sophisticated handler and trigger management that existing libraries lack:
Priority-Based Handler Execution
- Sequential Processing: Handlers execute in priority order with proper async handling
- Domain Isolation: Each context maintains its own handler registry
- Cross-Context Coordination: Controlled communication between domain contexts
- Result Collection: Aggregate results from multiple handlers for complex workflows
Intelligent Trigger System
- State-Change Triggers: Automatic triggers based on store value changes
- Cross-Context Triggers: Domain boundaries can trigger actions in other contexts
- Conditional Triggers: Smart triggers based on business rules and conditions
- Trigger Cleanup: Automatic cleanup prevents memory leaks and stale references
Key Benefits
- Document-Artifact Management: Direct relationship between documentation and implementation
- Domain Isolation: Each context maintains complete independence
- Type Safety: Full TypeScript support with domain-specific hooks
- Performance: Zero React re-renders with RefContext, selective updates with Stores
- Scalability: Easy to add new domains without affecting existing ones
- Team Collaboration: Different teams can work on different domains without conflicts
- Clear Boundaries: Perfect separation of concerns based on document domains
- Hardware Acceleration: Direct DOM manipulation with
translate3d()
for 60fps performance
1.1. RefContext Performance Architecture
Zero Re-render Philosophy
The RefContext pattern introduces a performance-first layer that bypasses React's rendering cycle entirely for DOM manipulation:
[User Interaction] → [Direct DOM Manipulation] → [Hardware Acceleration] → [60fps Updates]
↓
[No React Re-renders]
Core Performance Principles
- Direct DOM Access: Manipulate DOM elements directly without triggering React reconciliation
- Hardware Acceleration: Use
transform3d()
for GPU-accelerated animations - Separation of Concerns: Visual updates separated from business logic updates
- Memory Efficiency: Automatic cleanup and lifecycle management
- Type Safety: Full TypeScript support for DOM element types
Architecture Layers
// Performance Layer (RefContext)
┌─────────────────────────────────────────┐
│ Direct DOM Manipulation │
│ • Hardware Acceleration │
│ • Zero React Re-renders │
│ • 60fps Performance │
└─────────────────────────────────────────┘
// Business Logic Layer (Actions)
┌─────────────────────────────────────────┐
│ Action Pipeline │
│ • Side Effects │
│ • Coordination │
│ • Event Handling │
└─────────────────────────────────────────┘
// State Management Layer (Stores)
┌─────────────────────────────────────────┐
│ Reactive State │
│ • Data Management │
│ • Subscriptions │
│ • Type Safety │
└─────────────────────────────────────────┘
// View Layer (React Components)
┌─────────────────────────────────────────┐
│ React Rendering │
│ • Component Structure │
│ • Event Bindings │
│ • Provider Setup │
└─────────────────────────────────────────┘
Performance Comparison
Approach | React Re-renders | Performance | Memory | Complexity |
---|---|---|---|---|
useState | Every update | ~30fps | High GC | Simple |
useRef | Manual checks | ~45fps | Medium | Medium |
RefContext | Zero | 60fps+ | Low | Optimized |
RefContext Integration Patterns
Pattern 1: Pure Performance (RefContext Only)
const {
Provider: AnimationProvider,
useRefHandler: useAnimationRef
} = createRefContext<{
particle: HTMLDivElement;
canvas: HTMLCanvasElement;
}>('Animation');
// Zero React re-renders for animations
function ParticleAnimation() {
const particle = useAnimationRef('particle');
const animate = useCallback(() => {
if (particle.target) {
// Hardware accelerated animation
particle.target.style.transform = `translate3d(${x}px, ${y}px, 0)`;
}
}, [particle]);
return <div ref={particle.setRef} />;
}
Pattern 2: Hybrid Performance (RefContext + Stores)
// State for configuration
const { useStore: useConfigStore } = createDeclarativeStorePattern('Config', {
speed: 1.0,
color: '#ff0000'
});
// RefContext for performance-critical updates
const { useRefHandler: useAnimationRef } = createRefContext<{
element: HTMLDivElement;
}>('Animation');
function HybridComponent() {
const speedStore = useConfigStore('speed');
const element = useAnimationRef('element');
const speed = useStoreValue(speedStore); // React re-render only for config
const animate = useCallback(() => {
if (element.target) {
// Use config state but update DOM directly
element.target.style.transform = `translate3d(${x * speed}px, ${y}px, 0)`;
}
}, [element, speed]);
}
## 2. Domain-Specific Hooks Pattern (Core)
### Philosophy: Renamed Hook Pattern
The framework's core philosophy is to create **domain-specific hooks** through destructuring assignments, providing intuitive, type-safe APIs that improve developer experience.
```typescript
// ✅ Domain-specific hook naming pattern
export const {
Provider: UserBusinessProvider,
useStore: useUserBusinessStore, // Domain-specific store hook
useStoreManager: useUserBusinessStoreManager, // Domain-specific store registry hook
useStoreInfo: useUserBusinessStoreInfo
} = createDeclarativeStorePattern('UserBusiness', storeDefinitions);
export const {
Provider: UserBusinessActionProvider,
useActionDispatch: useUserBusinessAction, // Domain-specific action hook
useActionHandler: useUserBusinessActionHandler
} = createActionContext<UserBusinessActions>('UserBusinessAction');
Benefits of Domain-Specific Naming
- Type Safety: Full TypeScript inference with domain-specific types
- Developer Experience: Clear, autocomplete-friendly API
- Maintainability: Easy to identify which domain a hook belongs to
- Refactoring Safety: Type errors immediately highlight breaking changes
- Team Scalability: Different teams can work on different domains without conflicts
Store Access Patterns
Three valid patterns for accessing stores, each with specific use cases:
// Pattern 1: Domain-specific hooks (Components)
const store = useUserBusinessStore('profile');
// Pattern 2: Store manager access for advanced use cases (Handlers)
const storeManager = useUserBusinessStoreManager();
const store = storeManager.getStore('profile');
3. Basic Setup & Usage
Step 1: Define Domain Stores and Actions
// stores/userBusiness.store.ts
import { createDeclarativeStorePattern } from '@context-action/react';
import { createActionContext } from '@context-action/react';
// Define store interface
export interface UserBusinessData {
profile: {
id: string;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
};
preferences: {
theme: 'light' | 'dark';
language: string;
};
}
// Define action interface (ActionPayloadMap optional)
export interface UserBusinessActions {
updateProfile: {
data: Partial<UserBusinessData['profile']>;
validate?: boolean;
};
deleteUser: { userId: string };
}
// Create domain-specific store hooks
// Option 1: Type inference (current approach)
export const {
Provider: UserBusinessStoreProvider,
useStore: useUserBusinessStore,
useStoreManager: useUserBusinessStoreManager,
useStoreInfo: useUserBusinessStoreInfo
} = createDeclarativeStorePattern('UserBusiness', {
profile: {
initialValue: {
id: '',
name: '',
email: '',
role: 'guest'
}
},
preferences: {
initialValue: {
theme: 'light',
language: 'en'
}
}
});
// Option 2: Explicit generic types (new approach)
export const {
Provider: UserBusinessStoreProvider,
useStore: useUserBusinessStore,
useStoreManager: useUserBusinessStoreManager,
useStoreInfo: useUserBusinessStoreInfo
} = createDeclarativeStorePattern<UserBusinessData>('UserBusiness', {
// ⚠️ Note: Still requires InitialStores<T> structure even with explicit generics
profile: {
id: '',
name: '',
email: '',
role: 'guest'
},
preferences: {
// Can use direct values or config objects
initialValue: { theme: 'light', language: 'en' },
strategy: 'shallow'
}
});
// Create domain-specific action hooks
export const {
Provider: UserBusinessActionProvider,
useActionDispatch: useUserBusinessAction,
useActionHandler: useUserBusinessActionHandler,
useActionDispatchWithResult: useUserBusinessActionDispatchWithResult
} = createActionContext<UserBusinessActions>('UserBusinessAction');
Step 2: Provider Composition
// providers/UserProvider.tsx
import React, { FC } from 'react';
import {
UserBusinessStoreProvider,
UserBusinessActionProvider,
UserUIStoreProvider,
UserUIActionProvider
} from '@/stores';
// Compose all providers for the domain
export const UserProvider: FC<{ children: React.ReactNode }> = ({ children }) => (
<UserBusinessStoreProvider>
<UserUIStoreProvider>
<UserBusinessActionProvider>
<UserUIActionProvider>
{children}
</UserUIActionProvider>
</UserBusinessActionProvider>
</UserUIStoreProvider>
</UserBusinessStoreProvider>
);
// HOC pattern for cleaner composition (alternative)
export const withUserProviders = (Component: React.ComponentType) => {
return (props: any) => (
<UserProvider>
<Component {...props} />
</UserProvider>
);
};
Step 3: Component Implementation
// components/UserProfile.tsx
import React, { useCallback } from 'react';
import { useStoreValue } from '@context-action/react';
import { useUserBusinessStore, useUserBusinessAction } from '@/stores/userBusiness.store';
export function UserProfile() {
// Access domain-specific stores
const profileStore = useUserBusinessStore('profile');
const preferencesStore = useUserBusinessStore('preferences');
// Get current values
const profile = useStoreValue(profileStore);
const preferences = useStoreValue(preferencesStore);
// Get domain-specific action dispatcher
const dispatch = useUserBusinessAction();
const handleUpdateProfile = useCallback(async () => {
await dispatch('updateProfile', {
data: { name: 'New Name' },
validate: true
});
}, [dispatch]);
return (
<div>
<h2>{profile.name}</h2>
<p>Theme: {preferences.theme}</p>
<button onClick={handleUpdateProfile}>
Update Profile
</button>
</div>
);
}
4. Store Management
Store Creation and Access
// Store setup component
function UserStoreSetup() {
// Create stores in the context registry
useCreateUserBusinessStore('profile', {
id: '',
name: '',
email: '',
role: 'guest'
});
useCreateUserBusinessStore('preferences', {
theme: 'light',
language: 'en'
});
return null;
}
// Store access in components
function UserSettings() {
const preferencesStore = useUserBusinessStore('preferences');
const preferences = useStoreValue(preferencesStore);
const updateTheme = useCallback((theme: 'light' | 'dark') => {
preferencesStore.setValue({ ...preferences, theme });
}, [preferences, preferencesStore]);
return (
<div>
<button onClick={() => updateTheme('dark')}>
Switch to Dark Theme
</button>
</div>
);
}
Store Patterns
Singleton Behavior
Stores are singletons within Provider boundaries - same name returns same instance:
function ComponentA() {
const store = useUserBusinessStore('profile'); // Same instance
}
function ComponentB() {
const store = useUserBusinessStore('profile'); // Same instance as ComponentA
}
Lazy Evaluation in Handlers
Use stores.getStore() for lazy evaluation to avoid stale closures:
const handler = async (payload, controller) => {
// Lazy evaluation - gets current value at execution time
const profileStore = registry.getStore<UserProfile>('profile');
const currentProfile = profileStore.getValue();
// Business logic with current data
profileStore.setValue({ ...currentProfile, ...payload });
};
5. Action Handlers
Best Practice: useActionHandler Pattern
The recommended pattern for handler registration uses useActionHandler
+ useEffect
for optimal performance and proper cleanup:
import React, { useEffect, useCallback } from 'react';
import { useUserBusinessActionHandler, useUserBusinessStoreManager } from '@/stores/userBusiness.store';
function useUserBusinessHandlers() {
const storeManager = useUserBusinessStoreManager();
// Wrap handler with useCallback to prevent re-registration
const updateProfileHandler = useCallback(async (payload, controller) => {
// Lazy evaluation using store manager for current state
const profileStore = storeManager.getStore('profile');
const currentProfile = profileStore.getValue();
// Validation
if (payload.validate && !isValidEmail(payload.data.email)) {
controller.abort('Invalid email format');
return;
}
// Business logic
const updatedProfile = {
...currentProfile,
...payload.data,
updatedAt: Date.now()
};
// Update store
profileStore.setValue(updatedProfile);
// Return result for collection
return { success: true, profile: updatedProfile };
}, [storeManager]);
// Register handler using useActionHandler hook (actual API)
useUserBusinessActionHandler('updateProfile', updateProfileHandler, {
priority: 100, // Higher priority executes first
blocking: true, // Wait for async completion in sequential mode
tags: ['business'], // For filtering
id: 'profile-updater' // Explicit ID for debugging
});
}
Handler Registration Options
interface HandlerConfig {
priority?: number; // Execution order (higher = first)
blocking?: boolean; // Wait for async completion
tags?: string[]; // For filtering and categorization
id?: string; // Explicit handler ID
category?: string; // Handler category
returnType?: 'value'; // Enable return value collection
}
Handler Execution Flow
- Sequential Mode (default): Handlers run in priority order
- Parallel Mode: All handlers execute simultaneously
- Race Mode: First handler to complete wins
// Sequential with blocking
register('processOrder', handler1, { priority: 100, blocking: true });
register('processOrder', handler2, { priority: 90, blocking: true });
register('processOrder', handler3, { priority: 80, blocking: true });
// Execution: handler1 → waits → handler2 → waits → handler3
// Parallel execution
dispatch('processOrder', payload, { executionMode: 'parallel' });
Controller Methods
const handler = async (payload, controller) => {
// Abort pipeline
if (error) controller.abort('Error message');
// Jump to specific priority
if (urgent) controller.jumpToPriority(90);
// Set result for collection
controller.setResult(computedValue);
// Terminate pipeline with result
if (canFinishEarly) controller.return(finalResult);
};
Result Collection
function useOrderProcessing() {
const dispatchWithResult = useUserBusinessActionWithResult();
const processOrder = async (orderData) => {
const result = await dispatchWithResult('processOrder', orderData, {
result: {
collect: true, // Enable collection
strategy: 'all', // Collect all results
timeout: 5000, // 5 second timeout
maxResults: 10 // Limit results
},
filter: {
tags: ['validation', 'business'], // Only these handlers
excludeTags: ['logging'] // Exclude logging
}
});
if (result.success) {
console.log('Results:', result.results);
console.log('Duration:', result.execution.duration);
}
return result.result;
};
}
6. Advanced Patterns
Cross-Domain Integration (When Needed)
While domain isolation is preferred, sometimes cross-domain interaction is necessary:
// hooks/useUserCartIntegration.ts
export function useUserCartIntegration() {
// Access multiple domains
const userProfile = useUserBusinessStore('profile');
const cartItems = useCartStore('items');
const userAction = useUserBusinessAction();
const cartAction = useCartAction();
const profile = useStoreValue(userProfile);
const items = useStoreValue(cartItems);
const processCheckout = useCallback(async () => {
// Validate user
if (!profile.id) {
await userAction('requireLogin', {});
return;
}
// Process cart
await cartAction('processCheckout', {
userId: profile.id,
items: items
});
}, [profile.id, items, userAction, cartAction]);
return { processCheckout };
}
Logic Fit Hooks Pattern
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 uiAction = useUserUIAction();
const profile = useStoreValue(profileStore);
const view = useStoreValue(viewStore);
const startEditing = useCallback(() => {
uiAction('setEditMode', { editing: true });
}, [uiAction]);
const saveChanges = useCallback(async (data) => {
await businessAction('updateProfile', { data });
uiAction('setEditMode', { editing: false });
}, [businessAction, uiAction]);
return {
profile,
isEditing: view.isEditing,
startEditing,
saveChanges
};
}
Handler ID Strategies for Component Instances
When multiple instances of the same component exist:
function TodoItem({ todoId }: { todoId: string }) {
const componentId = useId(); // React's unique ID
const addHandler = useTodoActionHandler();
const handler = useCallback(async (payload) => {
// Handler logic specific to this instance
}, [todoId]);
useEffect(() => {
if (!addHandler) return;
// Unique ID per component instance
const unaddHandler = addHandler('updateTodo', handler, {
id: `updateTodo-${componentId}`,
cleanup: true
});
return unregister;
}, [register, handler, componentId]);
}
7. Best Practices
Handler Registration
- Always use
useActionHandler
hook for automatic registration and cleanup - Wrap handlers with
useCallback
to prevent re-registration - Use
blocking: true
for sequential async handlers - Consider explicit IDs for debugging and critical handlers
- Use
useActionRegister()
only for advanced manual control
Store Access
- Use domain-specific hooks in components
- Use
stores.getStore()
for lazy evaluation in handlers - Provide proper initial values, not null
- Keep store updates predictable and traceable
Type Safety (Recommended)
- Define interfaces for better type safety
- Use domain-specific hooks for type inference
- Avoid
any
types - leverage TypeScript
Performance
- Only subscribe to needed stores
- Use proper handler priorities
- Clean up handlers on unmount
- Use result collection selectively
Architecture
- One domain = One context boundary
- Separate business and UI concerns
- Prefer domain isolation, use cross-domain communication when necessary
- Document domain boundaries clearly
8. Common Pitfalls
❌ Missing Cleanup
// Wrong - No cleanup
useEffect(() => {
register('action', handler);
}, []);
// ✅ Correct - With cleanup
useEffect(() => {
if (!addHandler) return;
const unaddHandler = addHandler('action', handler);
return unregister; // Memory cleanup on unmount
}, [register, handler]);
❌ Missing blocking for Async Handlers
// Wrong - Handlers execute simultaneously
register('action', asyncHandler, { priority: 100 });
// ✅ Correct - Sequential execution
register('action', asyncHandler, {
priority: 100,
blocking: true // Wait for completion
});
❌ Using Stale Closures
// Wrong - Stale closure
const profile = profileStore.getValue();
const handler = async () => {
console.log(profile); // Stale value
};
// ✅ Correct - Lazy evaluation
const handler = async () => {
const profileStore = stores.getStore('profile');
const profile = profileStore.getValue(); // Current value
};
Architecture Diagrams
Provider Composition Pattern
graph TD
subgraph "Application Root"
App[App Component]
end
subgraph "User Domain Providers"
UBP[UserBusinessProvider]
UUP[UserUIProvider]
UBAP[UserBusinessActionProvider]
UUAP[UserUIActionProvider]
end
subgraph "Components"
UC[User Components]
end
App --> UBP
UBP --> UUP
UUP --> UBAP
UBAP --> UUAP
UUAP --> UC
style UBP fill:#e3f2fd
style UUP fill:#fff8e1
style UBAP fill:#e3f2fd
style UUAP fill:#fff8e1
Store Singleton Behavior
graph TB
subgraph "Provider Scope 1"
P1[Provider Instance]
S1[Store Registry - Singleton]
C1[Component A]
C2[Component B]
P1 --> S1
S1 --> C1
S1 --> C2
end
subgraph "Provider Scope 2"
P2[Provider Instance]
S2[Store Registry - Different Singleton]
C3[Component C]
C4[Component D]
P2 --> S2
S2 --> C3
S2 --> C4
end
Note[Same Provider = Same Store Instance<br/>Different Provider = Different Instance]
style S1 fill:#bbdefb
style S2 fill:#ffccbc
9. Implementation Examples
Quick Start Example
// 1. Define Domain (stores/user.store.ts)
export interface UserData {
profile: { id: string; name: string; email: string; role: 'admin' | 'user' | 'guest' };
preferences: { theme: 'light' | 'dark'; language: string };
}
export interface UserActions {
login: { email: string; password: string };
logout: void;
updateProfile: { data: Partial<UserData['profile']> };
}
// Create domain-specific hooks
export const {
Provider: UserProvider,
useStore: useUserStore,
useStoreManager: useUserStoreManager
} = createDeclarativeStorePattern('User', {
profile: { initialValue: { id: '', name: '', email: '', role: 'guest' } },
preferences: { initialValue: { theme: 'light', language: 'en' } }
});
export const {
Provider: UserActionProvider,
useActionDispatch: useUserAction,
useActionHandler: useUserActionHandler
} = createActionContext<UserActions>('UserAction');
// 2. Define Actions & Handlers (hooks/useUserHandlers.ts)
export function useUserHandlers() {
const storeManager = useUserStoreManager();
// Login handler example
const loginHandler = useCallback(async (payload, controller) => {
const profileStore = storeManager.getStore('profile');
try {
// Validation
if (!validateEmail(payload.email)) {
controller.abort('Invalid email format');
return;
}
// API call
const response = await authAPI.login(payload.email, payload.password);
// Update store
profileStore.setValue({
...response.user,
status: 'active'
});
return { success: true, userId: response.user.id };
} catch (error) {
controller.abort('Login failed', error);
return { success: false };
}
}, [storeManager]);
// Register handler using useActionHandler hook (actual API)
useUserActionHandler('login', loginHandler, {
priority: 100,
blocking: true,
id: 'user-login-handler'
});
// Similar patterns for logout, updateProfile handlers...
}
// 3. Use in Component (components/UserProfile.tsx)
export function UserProfile() {
const profileStore = useUserStore('profile');
const profile = useStoreValue(profileStore);
const dispatch = useUserAction();
const handleLogin = useCallback(async () => {
const result = await dispatch('login', {
email: 'user@example.com',
password: 'password123'
});
if (result?.success) {
console.log('Login successful');
}
}, [dispatch]);
const handleUpdateProfile = useCallback(() => {
dispatch('updateProfile', {
data: { name: 'New Name' }
});
}, [dispatch]);
return (
<div>
<h1>Welcome, {profile.name || 'Guest'}</h1>
<p>Email: {profile.email}</p>
<p>Role: {profile.role}</p>
{profile.id ? (
<button onClick={handleUpdateProfile}>Update Profile</button>
) : (
<button onClick={handleLogin}>Login</button>
)}
</div>
);
}
// 4. App Setup (App.tsx)
function App() {
return (
<UserProvider>
<UserActionProvider>
<UserHandlersSetup />
<UserProfile />
</UserActionProvider>
</UserProvider>
);
}
// Handler setup component
function UserHandlersSetup() {
useUserHandlers();
return null;
}
Logic Fit Hook Pattern (Combining Business & UI)
// hooks/useUserEditor.ts
export function useUserEditor() {
// Business layer
const profileStore = useUserStore('profile');
const profile = useStoreValue(profileStore);
const dispatch = useUserAction();
// UI state (local or separate UI store)
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState(profile);
// Combined logic
const startEditing = useCallback(() => {
setIsEditing(true);
setFormData(profile);
}, [profile]);
const saveChanges = useCallback(async () => {
const result = await dispatch('updateProfile', { data: formData });
if (result?.success) {
setIsEditing(false);
}
return result;
}, [dispatch, formData]);
const cancelEditing = useCallback(() => {
setIsEditing(false);
setFormData(profile);
}, [profile]);
return {
// Data
profile,
formData,
isEditing,
// Methods
startEditing,
saveChanges,
cancelEditing,
setFormData
};
}
Complete Implementation Pattern
For a full implementation with Business/UI separation, provider composition, and comprehensive handler patterns, see the GitHub repository examples.
Key patterns demonstrated:
- Store Definition: Business data, UI state, and domain isolation
- Handler Registration: Priority-based execution with cleanup
- Provider Composition: Nested providers for domain boundaries
- Logic Fit Hooks: Combining business and UI logic
- Testing Patterns: Unit and integration testing approaches
10. Testing Guide
Unit Testing Handlers
// __tests__/handlers/userBusinessHandlers.test.ts
import { renderHook } from '@testing-library/react-hooks';
import { useUserBusinessHandlers } from '@/hooks/handlers/useUserBusinessHandlers';
import { createMockRegistry, createMockController } from '@/test-utils';
describe('User Business Handlers', () => {
let mockRegistry;
let mockController;
beforeEach(() => {
mockRegistry = createMockRegistry();
mockController = createMockController();
});
it('should validate email in login handler', async () => {
const { result } = renderHook(() => useUserBusinessHandlers());
const loginHandler = result.current.loginHandler;
await loginHandler(
{ email: 'invalid-email', password: 'password123' },
mockController
);
expect(mockController.abort).toHaveBeenCalledWith('Invalid email format');
});
it('should update profile store on successful login', async () => {
const mockProfileStore = {
getValue: jest.fn(() => ({ id: '', name: '' })),
setValue: jest.fn()
};
mockRegistry.getStore.mockReturnValue(mockProfileStore);
const { result } = renderHook(() => useUserBusinessHandlers());
await result.current.loginHandler(
{ email: 'user@example.com', password: 'password123' },
mockController
);
expect(mockProfileStore.setValue).toHaveBeenCalledWith(
expect.objectContaining({
email: 'user@example.com',
status: 'active'
})
);
});
});
Integration Testing
// __tests__/integration/userFlow.test.tsx
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { UserProvider } from '@/providers/UserProvider';
import { UserProfile } from '@/components/UserProfile';
describe('User Profile Integration', () => {
it('should handle complete edit flow', async () => {
const { getByText, getByLabelText } = render(
<UserProvider>
<UserProfile />
</UserProvider>
);
// Start editing
fireEvent.click(getByText('Edit Profile'));
// Update fields
const nameInput = getByLabelText('Name:');
fireEvent.change(nameInput, { target: { value: 'New Name' } });
// Save changes
fireEvent.click(getByText('Save'));
// Verify updates
await waitFor(() => {
expect(getByText('Name: New Name')).toBeInTheDocument();
});
});
});
11. Migration Guide
From Legacy Patterns
// ❌ Old Pattern: Manual handler registration with useEffect
function OldComponent() {
const dispatch = useDispatch();
const register = useActionRegister();
const handler = () => {
// handler logic
};
useEffect(() => {
if (!register) return;
const unregister = register.register('action', handler);
return unregister;
}, [register, handler]);
}
// ✅ New Pattern: useActionHandler hook (current API)
function NewComponent() {
const dispatch = useAction();
const handler = useCallback(() => {
// handler logic
}, []);
// Handler automatically registered and cleaned up
useActionHandler('action', handler, {
id: 'unique-handler-id',
blocking: true
});
}
Incremental Migration Steps
Phase 1: Add Type Definitions
- Define interfaces for all stores and actions
- Add domain-specific hook exports
Phase 2: Update Handler Registration
- Convert to useActionHandler pattern
- Add cleanup functions
- Add explicit handler IDs
Phase 3: Implement Domain Isolation
- Separate business and UI concerns
- Create domain-specific providers
- Update component imports
Phase 4: Testing & Validation
- Update tests for new patterns
- Verify cleanup on unmount
- Performance testing
12. Troubleshooting
Common Issues and Solutions
Handler Not Executing
// Problem: Handler registered but not executing
register('action', asyncHandler, { priority: 100 });
// Solution: Add blocking for async handlers
register('action', asyncHandler, {
priority: 100,
blocking: true // Required for sequential async execution
});
Stale State in Handlers
// Problem: Getting old values
const value = store.getValue();
const handler = () => console.log(value); // Stale
// Solution: Use lazy evaluation
const handler = () => {
const store = stores.getStore('name');
const value = store.getValue(); // Current
};
Memory Leaks
// Problem: Manual registration without cleanup
useEffect(() => {
if (!register) return;
register.register('action', handler);
// No cleanup!
}, [register, handler]);
// Solution: Use useActionHandler hook (automatic cleanup)
const handler = useCallback(() => {
// handler logic
}, []);
useActionHandler('action', handler, {
id: 'action-handler',
blocking: true
}); // Cleanup handled automatically
Type Errors
// Problem: Type inference not working
const store = useStore('profile'); // Generic type
// Solution: Use domain-specific hooks
const store = useUserBusinessStore('profile'); // Typed
Conclusion
The Context-Action Store Integration Architecture provides a robust, scalable, and type-safe foundation for React applications. By following the patterns and best practices outlined in this guide, you can build maintainable applications with clear separation of concerns, excellent performance, and great developer experience.
Key Takeaways
- Use domain-specific hooks for type safety and clarity
- Use
useActionHandler
hook for automatic registration and cleanup - Use lazy evaluation in handlers to avoid stale state
- Separate business and UI concerns with different stores/actions
- Prefer domain isolation - use cross-domain only when necessary
- Consider explicit IDs for debugging and critical handlers
- Use blocking: true for sequential async handlers
- Test handlers in isolation before integration
For more information and updates, visit the project repository.