Action Basic Usage
Fundamental Action Only pattern with type-safe dispatching and handler registration.
Import
typescript
import { createActionContext } from '@context-action/react';Features
- ✅ Type-safe action dispatching
- ✅ Action handler registration
- ✅ Abort support
- ✅ Result handling
- ✅ Lightweight (no store overhead)
Prerequisites
Required Setup: Complete the following setup before using this pattern:
- Type Definitions - Define your action interfaces using the standard patterns
- Context Creation - Create typed action contexts with proper hook renaming
- Provider Configuration - Set up action providers in your app structure
For detailed setup instructions, see Basic Action Setup.
Required Action Types
This document uses the EventActions specification from the setup guide:
typescript
// From: Basic Action Setup → Common Action Patterns
interface EventActions {
userClick: { x: number; y: number };
userHover: { elementId: string };
analytics: { event: string; data: any };
trackInteraction: { type: string; metadata?: Record<string, any> };
}Required Context Setup
This document assumes you have created the Event action context:
typescript
// From: Basic Action Setup → Single Domain Context
const {
Provider: EventActionProvider,
useActionDispatch: useEventAction, // ← Renamed hook used in examples
useActionHandler: useEventActionHandler, // ← Renamed hook used in examples
useActionDispatchWithResult: useEventActionWithResult // ← For advanced features
} = createActionContext<EventActions>('Events');Required Provider Setup
This document assumes your app is wrapped with the Event action provider:
typescript
// From: Basic Action Setup → Single Provider Setup
function App() {
return (
<EventActionProvider>
<AppContent />
</EventActionProvider>
);
}Setup Reference: Basic Action Setup Guide
Basic Usage
tsx
// Component implementation using the configured context
function InteractiveComponent() {
const dispatch = useEventAction();
// Register action handlers (properly memoized)
const userClickHandler = useCallback((payload, controller) => {
console.log('User clicked at:', payload.x, payload.y);
// Pure side effects, no state management
}, []);
const analyticsHandler = useCallback(async (payload) => {
await fetch('/analytics', {
method: 'POST',
body: JSON.stringify(payload)
});
}, []);
useEventActionHandler('userClick', userClickHandler);
useEventActionHandler('analytics', analyticsHandler);
const handleClick = (e: MouseEvent) => {
dispatch('userClick', { x: e.clientX, y: e.clientY });
dispatch('analytics', { event: 'click', data: { timestamp: Date.now() } });
};
return <button onClick={handleClick}>Click Me</button>;
}Advanced Features
tsx
// Using the pre-configured context from Prerequisites setup
function AdvancedComponent() {
const {
dispatch,
dispatchWithResult,
abortAll
} = useEventActionWithResult();
const handleAsyncAction = async () => {
try {
const result = await dispatchWithResult('analytics', {
event: 'complex-operation',
data: { userId: 123 }
});
console.log('Action result:', result);
} catch (error) {
console.error('Action failed:', error);
}
};
const handleAbortAll = () => {
abortAll(); // Abort all pending actions
};
return (
<div>
<button onClick={handleAsyncAction}>Async Action</button>
<button onClick={handleAbortAll}>Abort All</button>
</div>
);
}Available Hooks
From Prerequisites Setup
useEventAction()- Basic action dispatcher (renamed from useActionDispatch)useEventActionHandler()- Register action handlers (renamed from useActionHandler)useEventActionWithResult()- Advanced dispatcher with results/abort (renamed from useActionDispatchWithResult)
Generic Pattern (before renaming)
useActionDispatch()- Basic action dispatcheruseActionHandler(action, handler, config?)- Register action handlersuseActionDispatchWithResult()- Advanced dispatcher with results/abortuseActionRegister()- Access raw ActionRegister for delegationuseActionContext()- Access raw context
Real-World Examples
Live Examples in Codebase
- Todo List Demo - UI Actions for form interactions
- Chat Demo - Real-time message handling
- User Profile Demo - User action management
- Mouse Events Page - High-frequency event handling
- Search Page - Abortable search actions
- API Blocking Page - Blocking action patterns
Error Handling Best Practices
Centralized Error Handling
tsx
// ✅ CORRECT: Use framework's centralized error handling
function ErrorHandlingComponent() {
const dispatch = useEventAction();
// Error-prone handler with proper error handling
const riskyOperationHandler = useCallback(async (payload, controller) => {
try {
// Perform risky operation
const result = await performRiskyAPICall(payload);
// Success case
console.log('Operation successful:', result);
} catch (error) {
// Framework provides centralized error handling
// No need for manual console.error - it's handled automatically
// Use controller to abort with context
controller.abort('Risky operation failed', error);
}
}, []);
// Event handler with error prevention
const safeEventHandler = useCallback(async (payload, controller) => {
// Never store DOM events or React synthetic events
const { clientX, clientY, timestamp } = payload;
// Extract only needed data, never store the event object itself
const eventData = {
position: { x: clientX, y: clientY },
timestamp,
action: 'user-interaction'
};
// Safe to process extracted data
await processEventData(eventData);
}, []);
useEventActionHandler('riskyOperation', riskyOperationHandler);
useEventActionHandler('safeEvent', safeEventHandler);
const handleClick = (event: MouseEvent) => {
// Extract event data before dispatching
dispatch('safeEvent', {
clientX: event.clientX,
clientY: event.clientY,
timestamp: Date.now()
// Don't pass the event object itself!
});
};
return <button onClick={handleClick}>Safe Click Handler</button>;
}Async Error Recovery
tsx
// ✅ CORRECT: Robust async error handling with retry logic
function AsyncErrorRecoveryComponent() {
const dispatch = useEventAction();
const retryableOperationHandler = useCallback(async (payload, controller) => {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const result = await performAsyncOperation(payload);
// Success - break retry loop
console.log(`Operation succeeded on attempt ${attempt + 1}`);
return result;
} catch (error) {
attempt++;
if (attempt >= maxRetries) {
// Final failure - let framework handle error centrally
controller.abort(`Operation failed after ${maxRetries} attempts`, error);
return;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
console.log(`Retrying operation, attempt ${attempt + 1}/${maxRetries}`);
}
}
}, []);
useEventActionHandler('retryableOperation', retryableOperationHandler);
return (
<button onClick={() => dispatch('retryableOperation', { data: 'test' })}>
Retry Operation
</button>
);
}Best Practices
✅ Best Practices
- Always Use useCallback: Wrap all handler functions with
useCallbackto prevent infinite re-registration - Handle Side Effects: Perfect for analytics, logging, API calls
- Keep Lightweight: No state management overhead
- Centralized Error Handling: Let framework handle errors automatically instead of manual console.error
- Event Data Extraction: Extract needed data from DOM events, never store event objects
- Async Error Recovery: Implement retry logic with proper error boundaries
- Controller Usage: Use controller.abort() for error cases with context
❌ Avoid
- Storing DOM events or React synthetic events in state or dispatching them
- Using direct console.error instead of letting framework handle errors centrally
- Blocking the main thread with heavy computations in handlers
- Missing cleanup for async operations or timers
Important: For detailed handler registration patterns, see the Handler Registration Guide