Form Lifecycle
Control form behavior throughout its lifecycle with hooks, state management, callbacks, and auto-save functionality.
Command Result Callbacks
Section titled “Command Result Callbacks”Handle command execution results with dedicated callbacks. These callbacks are invoked automatically after command execution based on the result state:
import { CommandForm } from '@cratis/arc/commands';import { ValidationResult } from '@cratis/arc/validation';
interface CreateUserResponse { userId: string; message: string;}
function UserForm() { const handleSuccess = (response: CreateUserResponse) => { console.log('User created with ID:', response.userId); // Navigate to user profile, show success message, etc. };
const handleFailed = (result: CommandResult<CreateUserResponse>) => { console.error('Command failed:', result); // Handle general failure };
const handleException = (messages: string[], stackTrace: string) => { console.error('Exception occurred:', messages); // Log exception, show error dialog, etc. };
const handleUnauthorized = () => { console.warn('User is not authorized'); // Redirect to login, show authorization message, etc. };
const handleValidationFailure = (validationResults: ValidationResult[]) => { console.warn('Validation failed:', validationResults); // Additional validation failure handling beyond automatic field errors };
return ( <CommandForm<CreateUser, CreateUserResponse> command={CreateUser} onSuccess={handleSuccess} onFailed={handleFailed} onException={handleException} onUnauthorized={handleUnauthorized} onValidationFailure={handleValidationFailure} > <InputTextField<CreateUser> value={c => c.name} title="Name" required /> <InputTextField<CreateUser> value={c => c.email} type="email" title="Email" required /> <button type="submit">Create User</button> </CommandForm> );}Available Callbacks
Section titled “Available Callbacks”| Callback | Parameters | When Invoked |
|---|---|---|
onSuccess | (response: TResponse) => void | Command executed successfully |
onFailed | (commandResult: CommandResult<TResponse>) => void | Command execution failed (any failure type) |
onException | (messages: string[], stackTrace: string) => void | Command threw an exception |
onUnauthorized | () => void | User is not authorized to execute the command |
onValidationFailure | (validationResults: ValidationResult[]) => void | Command failed validation |
Callback Invocation Order
Section titled “Callback Invocation Order”When a command fails, multiple callbacks may be invoked:
onFailed- Always called whenisSuccessisfalse- One or more specific callbacks based on failure type:
onExceptionifhasExceptionsistrueonUnauthorizedifisAuthorizedisfalseonValidationFailureifisValidisfalse
Type-Safe Response Handling
Section titled “Type-Safe Response Handling”CommandForm supports generic type parameters for type-safe responses:
// Define response typeinterface OrderResponse { orderId: string; orderNumber: string; totalAmount: number;}
// Use generic type parameters<CommandForm<CreateOrder, OrderResponse> command={CreateOrder} onSuccess={(response) => { // response is strongly typed as OrderResponse console.log(`Order ${response.orderNumber} created with ID ${response.orderId}`); }}> {/* Form fields */}</CommandForm>Integration with Navigation
Section titled “Integration with Navigation”Common pattern for navigating after successful command execution:
import { useNavigate } from 'react-router-dom';
function CreateProjectForm() { const navigate = useNavigate();
const handleSuccess = (response: ProjectResponse) => { // Navigate to the newly created project navigate(`/projects/${response.projectId}`); };
return ( <CommandForm<CreateProject, ProjectResponse> command={CreateProject} onSuccess={handleSuccess} > <InputTextField<CreateProject> value={c => c.name} title="Name" required /> <TextAreaField<CreateProject> value={c => c.description} title="Description" /> <button type="submit">Create Project</button> </CommandForm> );}Before Execute Hook
Section titled “Before Execute Hook”Execute code before the command is submitted:
function OrderForm() { const handleBeforeExecute = async (command: CreateOrder): Promise<boolean> => { // Confirm before submitting const confirmed = window.confirm('Submit this order?');
if (!confirmed) { return false; // Cancel submission }
// Add calculated fields command.totalAmount = calculateTotal(command.items); command.submittedAt = new Date();
return true; // Proceed with submission };
return ( <CommandForm command={CreateOrder} beforeExecute={handleBeforeExecute} > <InputTextField<CreateOrder> value={c => c.customerName} title="Customer" required /> {/* More fields... */} </CommandForm> );}The beforeExecute callback:
- Receives the command instance
- Returns
trueto proceed,falseto cancel - Can modify the command before submission
- Can perform async operations
Form State Management
Section titled “Form State Management”Track form state for enhanced UX:
function SmartForm() { const command = useCommandInstance(UpdateProfile); const [isSubmitting, setIsSubmitting] = useState(false); const [lastSaved, setLastSaved] = useState<Date | null>(null);
const handleExecute = async () => { setIsSubmitting(true); try { const result = await command.execute(); if (result.isSuccess) { setLastSaved(new Date()); } } finally { setIsSubmitting(false); } };
return ( <div> <CommandForm command={UpdateProfile}> <InputTextField<UpdateProfile> value={c => c.name} title="Name" /> <InputTextField<UpdateProfile> value={c => c.email} type="email" title="Email" /> </CommandForm>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '1rem' }}> <button onClick={handleExecute} disabled={!command.hasChanges || isSubmitting} > {isSubmitting ? 'Saving...' : 'Save'} </button>
{lastSaved && ( <span style={{ fontSize: '0.875rem', color: '#6b7280' }}> Last saved: {lastSaved.toLocaleTimeString()} </span> )}
{command.hasChanges && ( <span style={{ fontSize: '0.875rem', color: '#f59e0b' }}> Unsaved changes </span> )} </div> </div> );}Auto-Save
Section titled “Auto-Save”Implement auto-save functionality:
function AutoSaveForm() { const command = useCommandInstance(UpdateDraft); const timeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => { if (command.hasChanges) { // Clear previous timeout if (timeoutRef.current) { clearTimeout(timeoutRef.current); }
// Set new timeout to auto-save after 2 seconds timeoutRef.current = setTimeout(async () => { await command.execute(); console.log('Auto-saved'); }, 2000); }
return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, [command.hasChanges]);
return ( <CommandForm command={UpdateDraft}> <InputTextField<UpdateDraft> value={c => c.title} title="Title" /> <TextAreaField<UpdateDraft> value={c => c.content} title="Content" rows={10} /> <div style={{ fontSize: '0.875rem', color: '#6b7280', marginTop: '0.5rem' }}> Changes are saved automatically </div> </CommandForm> );}