Validation
CommandForm integrates seamlessly with the Arc command validation system to provide automatic validation feedback and error handling.
Overview
Section titled “Overview”CommandForm automatically validates field inputs and displays errors based on:
- Required Fields: Fields marked with
requiredprop - Type Validation: Built-in HTML5 validation (email, URL, number ranges, etc.)
- Command Validation Rules: Backend validation rules defined on your command
- Custom Validation: Custom validators you define
Validation Timing
Section titled “Validation Timing”CommandForm provides flexible control over when validation occurs through the validateOn prop:
Validate on Blur (Default)
Section titled “Validate on Blur (Default)”By default, validation occurs when a field loses focus (blur event). This provides a balance between immediate feedback and not interrupting the user while typing:
<CommandForm command={RegisterUser} validateOn="blur"> <InputTextField<RegisterUser> value={c => c.email} type="email" title="Email" required /> <InputTextField<RegisterUser> value={c => c.password} type="password" title="Password" required /></CommandForm>When to use: Most forms - provides feedback after the user completes a field without being intrusive.
Validate on Change
Section titled “Validate on Change”Validation runs immediately as the user types. This provides the fastest feedback but can be distracting:
<CommandForm command={RegisterUser} validateOn="change"> <InputTextField<RegisterUser> value={c => c.email} type="email" title="Email" required /> <InputTextField<RegisterUser> value={c => c.password} type="password" title="Password" required /></CommandForm>When to use: Forms where immediate validation is critical, like password strength meters or username availability checks.
Validate on Both
Section titled “Validate on Both”Validation runs on both change and blur events:
<CommandForm command={RegisterUser} validateOn="both"> <InputTextField<RegisterUser> value={c => c.email} type="email" title="Email" required /> <InputTextField<RegisterUser> value={c => c.password} type="password" title="Password" required /></CommandForm>When to use: Forms where continuous validation is important for complex rules.
Validation Scope
Section titled “Validation Scope”Control whether validation validates just the changed field or all fields:
Per-Field Validation (Default)
Section titled “Per-Field Validation (Default)”By default, only the field that changed is validated. This is more efficient and provides focused feedback:
<CommandForm command={CreateUser} validateOn="blur"> <InputTextField<CreateUser> value={c => c.username} title="Username" required /> <InputTextField<CreateUser> value={c => c.email} type="email" title="Email" required /></CommandForm>In this example, when the username field loses focus, only username validation runs.
Full Form Validation
Section titled “Full Form Validation”Set validateAllFieldsOnChange to true to validate the entire form when any field changes:
<CommandForm command={CreateUser} validateOn="blur" validateAllFieldsOnChange={true}> <InputTextField<CreateUser> value={c => c.username} title="Username" required /> <InputTextField<CreateUser> value={c => c.email} type="email" title="Email" required /> <InputTextField<CreateUser> value={c => c.confirmEmail} type="email" title="Confirm Email" required /></CommandForm>When to use: Forms with interdependent fields where one field’s validity depends on another (e.g., password confirmation, start/end dates).
Silent Validation on Load
Section titled “Silent Validation on Load”CommandForm always runs client validation silently on load, regardless of any other settings. This means isValid in the form context correctly reflects the real validity state from the very first render — before the user has interacted with any field.
This is useful for scenarios such as:
- Disabling a submit button until the form is valid
- Conditionally rendering actions based on form state
- Knowing whether previously loaded data is valid before the user touches anything
function EditUserForm({ user }: { user: User }) { return ( <CommandForm command={UpdateUser} currentValues={user}> <InputTextField<UpdateUser> value={c => c.username} title="Username" required /> <InputTextField<UpdateUser> value={c => c.email} type="email" title="Email" required /> <SubmitButton /> {/* can use isValid from useCommandFormContext */} </CommandForm> );}The silent validation does not display any error messages. Errors are only rendered after the user has interacted with a field (governed by validateOn) or when validateOnInit is set to true.
Showing Errors on Load (validateOnInit)
Section titled “Showing Errors on Load (validateOnInit)”Set validateOnInit to true to show validation error messages immediately when the form renders:
<CommandForm command={CreateUser} validateOnInit={true} initialValues={{ username: '', email: '' }}> <InputTextField<CreateUser> value={c => c.username} title="Username" required /> <InputTextField<CreateUser> value={c => c.email} type="email" title="Email" required /></CommandForm>When to use:
- Edit forms where existing data might have validation issues
- Forms where you want to show all errors upfront
- Step-by-step wizards showing validation state of upcoming steps
For automatic server-side validation as users type, see Auto Server Validation.
Validation Options Summary
Section titled “Validation Options Summary”| Prop | Type | Default | Description |
|---|---|---|---|
validateOn | 'blur' | 'change' | 'both' | 'blur' | When to trigger validation |
validateAllFieldsOnChange | boolean | false | Validate all fields or just the changed field |
validateOnInit | boolean | false | Show validation error messages on form initialization (validation itself always runs silently on load) |
Examples
Section titled “Examples”Gentle validation (recommended for most forms):
<CommandForm command={T} validateOn="blur" />Note:
isValidin context is already accurate on first render due to silent validation on load, even though no errors are displayed yet.
Aggressive validation (real-time feedback):
<CommandForm command={T} validateOn="change" validateAllFieldsOnChange={true} />Show all errors immediately:
<CommandForm command={T} validateOn="blur" validateOnInit={true} />Required Fields
Section titled “Required Fields”Mark fields as required using the required prop:
<CommandForm command={RegisterUser}> <InputTextField<RegisterUser> value={c => c.email} type="email" title="Email" required /> <InputTextField<RegisterUser> value={c => c.password} type="password" title="Password" required /> <CheckboxField<RegisterUser> value={c => c.agreeToTerms} title="Terms" label="I agree" required /></CommandForm>Required fields:
- Show visual indicator when invalid
- Prevent form submission when empty
- Display error messages when validation fails
Automatic Error Display
Section titled “Automatic Error Display”By default, CommandForm displays error messages below each invalid field:
// Errors shown automatically for invalid/required fields<CommandForm command={CreateAccount}> <InputTextField<CreateAccount> value={c => c.username} title="Username" required /> {/* Error appears here if username is empty or invalid */}
<InputTextField<CreateAccount> value={c => c.email} type="email" title="Email" required /> {/* Error appears here if email is invalid format */}</CommandForm>Disabling Error Display
Section titled “Disabling Error Display”Disable automatic errors to implement custom error rendering:
<CommandForm command={CreateAccount} showErrors={false}> <InputTextField<CreateAccount> value={c => c.username} title="Username" required /> {/* No automatic error rendering */}</CommandForm>See Customization for custom error rendering patterns.
HTML5 Validation
Section titled “HTML5 Validation”Field components leverage HTML5 validation attributes:
<CommandForm command={UpdateProfile}> {/* Email format validation */} <InputTextField<UpdateProfile> value={c => c.email} type="email" title="Email" required />
{/* URL format validation */} <InputTextField<UpdateProfile> value={c => c.website} type="url" title="Website" placeholder="https://example.com" />
{/* Number range validation */} <NumberField<UpdateProfile> value={c => c.age} title="Age" min={18} max={120} required />
{/* Pattern matching */} <InputTextField<UpdateProfile> value={c => c.phone} type="tel" title="Phone Number" /></CommandForm>Backend Validation
Section titled “Backend Validation”CommandForm automatically propagates validation results from backend command handlers:
Command Definition (C#)
Section titled “Command Definition (C#)”[Command]public record CreateUser(string Email, string Username){ public Result<ValidationResult, UserRegistered> Handle() { // Backend validation if (Username.Length < 3) { return ValidationResult.Error("Username must be at least 3 characters"); }
if (!Email.Contains('@')) { return ValidationResult.Error("Email must be a valid address"); }
return new UserRegistered(Email, Username); }}Form Usage
Section titled “Form Usage”<CommandForm command={CreateUser}> <InputTextField<CreateUser> value={c => c.email} type="email" title="Email" required /> <InputTextField<CreateUser> value={c => c.username} title="Username" required /></CommandForm>When the form is submitted:
- Frontend validation runs first (required, type checking)
- Command is sent to backend if frontend validation passes
- Backend validation rules execute
- Validation errors are returned and displayed in the form
- The form remains interactive for corrections
Accessing Validation State
Section titled “Accessing Validation State”Use the useCommandFormContext hook to access validation state programmatically:
import { useCommandFormContext } from '@cratis/arc.react/commands';
function MyForm() { const { getFieldError, commandResult } = useCommandFormContext(); const emailError = getFieldError('email'); const hasAnyErrors = commandResult?.validationResults && commandResult.validationResults.length > 0;
return ( <CommandForm command={CreateAccount} showErrors={false}> <InputTextField<CreateAccount> value={c => c.email} type="email" title="Email" required />
{/* Check for specific field errors */} {emailError && ( <div className="error"> {emailError} </div> )}
<InputTextField<CreateAccount> value={c => c.username} title="Username" required />
{/* Check for any errors */} {hasAnyErrors && ( <div className="error-summary"> Please fix the errors above before submitting. </div> )} </CommandForm> );}Progressive Validation
Section titled “Progressive Validation”Validate as users interact with the form using the useCommandInstance hook:
import { useCommandInstance } from '@cratis/arc.react/commands';import { useEffect } from 'react';
function MyForm() { const command = useCommandInstance(CreateAccount); const [canSubmit, setCanSubmit] = useState(false);
useEffect(() => { // Validate whenever command changes const validate = async () => { const result = await command.validate(); setCanSubmit(result.isValid); };
if (command.hasChanges) { validate(); } }, [command.hasChanges]);
return ( <CommandForm command={CreateAccount}> <InputTextField<CreateAccount> value={c => c.email} type="email" title="Email" required /> <InputTextField<CreateAccount> value={c => c.username} title="Username" required />
<button type="submit" disabled={!canSubmit}> Create Account </button> </CommandForm> );}Field-Level Validation
Section titled “Field-Level Validation”Validate individual fields on blur for immediate feedback:
function RegistrationForm() { const command = useCommandInstance(RegisterUser); const [emailError, setEmailError] = useState<string>();
const handleEmailBlur = async () => { // Validate just the email field const result = await command.validate();
if (result.hasErrors('email')) { setEmailError(result.getErrorsFor('email')[0]); } else { setEmailError(undefined); } };
return ( <CommandForm command={RegisterUser} showErrors={false}> <InputTextField<RegisterUser> value={c => c.email} type="email" title="Email" required onBlur={handleEmailBlur} /> {emailError && <div className="error">{emailError}</div>} </CommandForm> );}Validation Results
Section titled “Validation Results”The command validate() method returns a CommandResult with:
interface CommandResult { isSuccess: boolean; isValid: boolean; isAuthorized: boolean; validationResults: ValidationResult[]; errors: Record<string, string[]>;
hasErrors(property?: string): boolean; getErrorsFor(property: string): string[];}Example
Section titled “Example”const result = await command.validate();
if (!result.isValid) { console.log('Validation failed'); console.log('All errors:', result.errors); console.log('Email errors:', result.getErrorsFor('email'));}
if (!result.isAuthorized) { console.log('User not authorized to execute this command');}Best Practices
Section titled “Best Practices”- Use Required Fields: Mark essential fields with
requiredfor client-side validation - Type-Specific Fields: Use appropriate input types (email, url, number) for built-in validation
- Backend Validation: Always validate on the server for security and data integrity
- Progressive Feedback: Consider validating on field blur for better UX
- Clear Messages: Provide clear, actionable error messages
- Accessible Errors: Ensure error messages are associated with their fields for screen readers
- Visual Feedback: Use consistent visual styling for invalid fields
Command Validation System
Section titled “Command Validation System”For comprehensive details on the command validation system:
- TypeScript/React: See Core Validation
- Backend: See Backend Command Validation
- Command Usage: See Commands Overview