Imperative Usage
While the React Hook Usage is the recommended approach for React components, there are scenarios where you need more direct control or are working outside of React’s component lifecycle. This guide covers imperative command usage.
Overview
Section titled “Overview”Imperative usage involves directly instantiating and manipulating command objects without React hooks. This is useful for:
- Non-React Code: Service layers, utility functions, or vanilla JavaScript
- Event Handlers: Complex operations in event handlers
- Testing: Unit tests and integration tests
- Advanced Scenarios: Custom command orchestration or batching
Basic Imperative Usage
Section titled “Basic Imperative Usage”Directly instantiate and execute a command:
import { OpenDebitAccount } from './generated/commands';
const command = new OpenDebitAccount();command.accountId = 'a23edccc-6cb5-44fd-a7a7-7563716fb080';command.name = 'My Account';command.owner = '84cda809-9201-4d8c-8589-0be37c6e3f18';
const result = await command.execute();
if (result.isSuccess) { console.log('Account opened successfully');}Setting Initial Values
Section titled “Setting Initial Values”For change tracking to work, set initial values using setInitialValues():
const command = new OpenDebitAccount();
command.setInitialValues({ accountId: 'a23edccc-6cb5-44fd-a7a7-7563716fb080', name: 'My Account', owner: '84cda809-9201-4d8c-8589-0be37c6e3f18'});
// At this point hasChanges is falseconsole.log(command.hasChanges); // false
command.name = 'My other account';// Now hasChanges is trueconsole.log(command.hasChanges); // trueUse Cases
Section titled “Use Cases”Service Layer Functions
Section titled “Service Layer Functions”export class AccountService { async openAccount(accountData: { accountId: string; name: string; owner: string }) { const command = new OpenDebitAccount(); command.accountId = accountData.accountId; command.name = accountData.name; command.owner = accountData.owner;
const result = await command.execute();
if (!result.isSuccess) { throw new Error('Failed to open account: ' + result.exceptionMessages.join(', ')); }
return result.response; }}Batch Operations
Section titled “Batch Operations”async function batchOpenAccounts(accounts: Array<{ accountId: string; name: string; owner: string }>) { const results = await Promise.all( accounts.map(async (data) => { const command = new OpenDebitAccount(); command.accountId = data.accountId; command.name = data.name; command.owner = data.owner; return command.execute(); }) );
const successful = results.filter(r => r.isSuccess); const failed = results.filter(r => !r.isSuccess);
return { successful: successful.length, failed: failed.length, failedReasons: failed.map(r => r.exceptionMessages) };}Event Handler
Section titled “Event Handler”async function handleAccountCreation(event: CustomEvent) { const { accountId, name, owner } = event.detail;
const command = new OpenDebitAccount(); command.accountId = accountId; command.name = name; command.owner = owner;
const result = await command.execute();
if (result.isSuccess) { // Trigger success event window.dispatchEvent(new CustomEvent('account-opened', { detail: result.response })); } else { // Trigger error event window.dispatchEvent(new CustomEvent('account-error', { detail: result.exceptionMessages })); }}Testing
Section titled “Testing”import { describe, it, expect } from 'vitest';import { OpenDebitAccount } from './generated/commands';
describe('OpenDebitAccount', () => { it('should successfully open an account with valid data', async () => { const command = new OpenDebitAccount(); command.accountId = 'test-account-id'; command.name = 'Test Account'; command.owner = 'test-owner-id';
const result = await command.execute();
expect(result.isSuccess).toBe(true); expect(result.isValid).toBe(true); expect(result.isAuthorized).toBe(true); });
it('should track changes correctly', () => { const command = new OpenDebitAccount(); command.setInitialValues({ accountId: 'test-id', name: 'Original Name', owner: 'owner-id' });
expect(command.hasChanges).toBe(false);
command.name = 'Modified Name'; expect(command.hasChanges).toBe(true);
command.name = 'Original Name'; expect(command.hasChanges).toBe(false); });
it('should validate without executing', async () => { const command = new OpenDebitAccount(); command.accountId = ''; command.name = ''; command.owner = '';
const result = await command.validate();
expect(result.isValid).toBe(false); expect(result.validationResults.length).toBeGreaterThan(0); });});Validation
Section titled “Validation”Commands can be validated imperatively without execution:
const command = new OpenDebitAccount();command.accountId = 'test-id';command.name = ''; // Invalid - empty namecommand.owner = 'owner-id';
const validationResult = await command.validate();
if (!validationResult.isValid) { console.error('Validation failed:'); validationResult.validationResults.forEach(error => { console.error(`- ${error.members.join(', ')}: ${error.message}`); });}See Validation for more details.
Using with Factories
Section titled “Using with Factories”Create factory functions for common command patterns:
function createAccountCommand(data: { accountId: string; name: string; owner: string; initialBalance?: number;}): OpenDebitAccount { const command = new OpenDebitAccount(); command.accountId = data.accountId; command.name = data.name; command.owner = data.owner;
// Set initial values for change tracking command.setInitialValues(data);
return command;}
// Usageconst command = createAccountCommand({ accountId: crypto.randomUUID(), name: 'Savings Account', owner: 'user-123'});
await command.execute();Error Handling
Section titled “Error Handling”Always handle errors properly with imperative usage:
async function executeCommand(command: OpenDebitAccount) { try { const result = await command.execute();
if (!result.isSuccess) { if (!result.isAuthorized) { throw new Error('Unauthorized'); } if (!result.isValid) { const errors = result.validationResults.map(v => v.message).join(', '); throw new Error(`Validation failed: ${errors}`); } if (result.hasExceptions) { throw new Error(result.exceptionMessages.join(', ')); } }
return result.response; } catch (error) { console.error('Command execution failed:', error); throw error; }}Integration with React (Advanced)
Section titled “Integration with React (Advanced)”While React hooks are preferred, you can use imperative commands within React components for specific scenarios:
import { useState } from 'react';import { OpenDebitAccount } from './generated/commands';
export const AccountCreator = () => { const [processing, setProcessing] = useState(false);
const handleQuickCreate = async () => { setProcessing(true);
// Create and execute command imperatively const command = new OpenDebitAccount(); command.accountId = crypto.randomUUID(); command.name = 'Quick Account'; command.owner = 'default-owner';
try { const result = await command.execute(); if (result.isSuccess) { alert('Account created!'); } } finally { setProcessing(false); } };
return ( <button onClick={handleQuickCreate} disabled={processing}> Quick Create Account </button> );};Note: For React components, prefer using the React Hook Usage approach, as it provides automatic re-rendering and better integration with React’s lifecycle.
Best Practices
Section titled “Best Practices”- Use React Hooks in Components: Only use imperative approach when necessary
- Handle All Result States: Check
isSuccess,isValid,isAuthorized - Set Initial Values: Call
setInitialValues()when you need change tracking - Error Handling: Always handle errors appropriately
- Type Safety: Leverage TypeScript for type checking
- Testing: Imperative usage is excellent for unit testing
- Documentation: Document why imperative usage was chosen over hooks
When NOT to Use Imperative Usage
Section titled “When NOT to Use Imperative Usage”Avoid imperative usage when:
- You’re in a React component (use hooks instead)
- You need automatic re-rendering
- You want React lifecycle integration
- Building forms (use CommandForm)
See Also
Section titled “See Also”- Commands Overview
- React Hook Usage - Recommended approach for React
- Data Binding
- Validation
- CommandForm
- Core Commands - Lower-level command concepts