Skip to content

CommandDialog - Advanced Features

CommandDialog supports typed command responses and provides callbacks for different execution outcomes:

import { CommandDialog } from '@cratis/components/CommandDialog';
import { ValidationResult } from '@cratis/arc/validation';
type CreateUserResponse = {
userId: string;
username: string;
message: string;
};
<CommandDialog<CreateUser, CreateUserResponse>
command={CreateUser}
title="Create User"
onSuccess={(response) => {
// Handle successful creation - response is fully typed
console.log(`User created with ID: ${response.userId}`);
showNotification(response.message);
navigate(`/users/${response.userId}`);
}}
onFailed={(commandResult) => {
// Handle any failure - includes all failure details
console.error('Command failed:', commandResult);
}}
onException={(messages, stackTrace) => {
// Handle exceptions specifically
console.error('Exception occurred:', messages.join(', '));
console.error('Stack trace:', stackTrace);
}}
onUnauthorized={() => {
// Handle authorization failures
showNotification('You are not authorized to perform this action');
navigate('/login');
}}
onValidationFailure={(validationResults) => {
// Handle validation failures
const errors = validationResults.map(r => r.message).join(', ');
showNotification(`Validation failed: ${errors}`);
}}
/>

Multiple callbacks may fire for the same command execution:

  1. onSuccess: Only fires when commandResult.isSuccess is true
  2. onFailed: Fires for any failure (validation, exception, unauthorized, etc.)
  3. onException: Fires specifically when an exception occurs
  4. onUnauthorized: Fires specifically when authorization fails
  5. onValidationFailure: Fires specifically when validation fails

For example, a validation failure will trigger both onFailed and onValidationFailure.

The response type parameter is optional and defaults to object:

// Explicit response type
<CommandDialog<CreateUser, CreateUserResponse>
command={CreateUser}
onSuccess={(response) => {
// response is CreateUserResponse
}}
/>
// Default object response type
<CommandDialog<CreateUser>
command={CreateUser}
onSuccess={(response) => {
// response is object
}}
/>

Provide custom validation logic for individual fields:

const validateField = (command, fieldName, oldValue, newValue) => {
if (fieldName === 'email' && !newValue.includes('@')) {
return 'Invalid email address';
}
return undefined;
};
<CommandDialog
onFieldValidate={validateField}
// ... other props
/>

Transform command values before execution:

const transformBeforeExecute = (values) => {
return {
...values,
timestamp: new Date()
};
};
<CommandDialog
onBeforeExecute={transformBeforeExecute}
// ... other props
/>

React to field value changes:

const handleFieldChange = (command, fieldName, oldValue, newValue) => {
console.log(`${fieldName} changed from ${oldValue} to ${newValue}`);
};
<CommandDialog
onFieldChange={handleFieldChange}
// ... other props
/>

Combining multiple validation patterns:

const validateField = (command, fieldName, oldValue, newValue) => {
switch (fieldName) {
case 'email':
if (!newValue || !newValue.includes('@')) {
return 'Valid email address is required';
}
break;
case 'age':
if (newValue < 18) {
return 'Must be at least 18 years old';
}
if (newValue > 120) {
return 'Please enter a valid age';
}
break;
case 'password':
if (newValue.length < 8) {
return 'Password must be at least 8 characters';
}
if (!/[A-Z]/.test(newValue)) {
return 'Password must contain an uppercase letter';
}
if (!/[0-9]/.test(newValue)) {
return 'Password must contain a number';
}
break;
}
return undefined;
};

Validate fields based on other field values:

const validateField = (command, fieldName, oldValue, newValue) => {
if (fieldName === 'confirmPassword') {
if (newValue !== command.password) {
return 'Passwords do not match';
}
}
if (fieldName === 'endDate') {
if (newValue < command.startDate) {
return 'End date must be after start date';
}
}
return undefined;
};

Update other fields when one field changes:

const handleFieldChange = (command, fieldName, oldValue, newValue) => {
if (fieldName === 'country' && newValue === 'USA') {
// Could trigger state updates or side effects
console.log('Country changed to USA, update state list');
}
if (fieldName === 'quantity') {
// Calculate derived values
const total = newValue * command.pricePerUnit;
console.log('New total:', total);
}
};

For validation that requires API calls:

const validateField = async (command, fieldName, oldValue, newValue) => {
if (fieldName === 'username') {
const isAvailable = await checkUsernameAvailability(newValue);
if (!isAvailable) {
return 'Username is already taken';
}
}
return undefined;
};

Common transformation scenarios:

const transformBeforeExecute = (values) => {
return {
...values,
// Add metadata
timestamp: new Date(),
userId: getCurrentUserId(),
// Normalize data
email: values.email.toLowerCase().trim(),
// Convert formats
dateOfBirth: new Date(values.dateOfBirth),
// Remove UI-only fields
confirmPassword: undefined,
// Calculate derived values
totalPrice: values.quantity * values.pricePerUnit
};
};