Atlas Forms
Building the EditComponent
The EditComponent is the most important component in your control. It renders in edit and admin modes — the interactive state where users enter data. It receives the current value, fires change events, and displays validation errors.
Props Contract
interface EditComponentProps {
control: FormControl; // Full control config — use control.settings for custom config
value: any; // Current value from form state
onChange: (value: any) => void; // Must call this when the value changes
onBlur: () => void; // Must call this when the input loses focus
error?: string; // Validation error — display prominently if present
mode: FormOperatingMode; // 'edit' | 'admin' | 'design' | 'preview'
disabled?: boolean; // Grey out and prevent interaction
readOnly?: boolean; // Display value but don't allow changes
}
Phone Input — Complete Example
import React, { useCallback } from 'react';
import type { EditComponentProps } from '@atlas-forms/types-js';
export const PhoneEditComponent: React.FC<EditComponentProps> = ({
control,
value,
onChange,
onBlur,
error,
disabled,
readOnly,
}) => {
const settings = control.settings ?? {};
const defaultCountry = settings.defaultCountry ?? 'US';
const showFlag = settings.showFlag ?? true;
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value);
}, [onChange]);
return (
<div className="phone-input-wrapper">
{showFlag && (
<span className={`flag flag-${defaultCountry.toLowerCase()}`} aria-hidden="true" />
)}
<input
type="tel"
value={value ?? ''}
onChange={handleChange}
onBlur={onBlur}
disabled={disabled}
readOnly={readOnly}
placeholder={control.description ?? 'Enter phone number'}
aria-label={control.label}
aria-invalid={!!error}
aria-describedby={error ? `${control.id}-error` : undefined}
className={error ? 'has-error' : ''}
/>
{error && (
<span
id={`${control.id}-error`}
className="field-error"
role="alert"
>
{error}
</span>
)}
</div>
);
};
Key Rules for EditComponent
- Always pass aria attributes —
aria-label,aria-invalid,aria-describedbyfor screen reader support - Call
onChangeon every value change — Do not buffer or debounce before callingonChange(the form engine handles debouncing for auto-save) - Call
onBluron focus loss — This triggers validation inonBlurvalidation mode - Handle
nullandundefinedvalues — Usevalue ?? ''for text inputs to avoid uncontrolled-to-controlled warnings - Respect
disabledandreadOnly— Both props must be honoured - Read custom settings from
control.settings— Not from a prop; the config lives in the form schema
Handling Complex Values
For controls that manage structured data (object or array values):
// Address picker — value is an object
interface AddressValue {
street: string;
city: string;
state: string;
zipCode: string;
}
const AddressEdit: React.FC<EditComponentProps> = ({ value, onChange }) => {
const address = (value as AddressValue) ?? { street: '', city: '', state: '', zipCode: '' };
const update = (field: keyof AddressValue, fieldValue: string) => {
onChange({ ...address, [field]: fieldValue });
};
return (
<div className="address-picker">
<input value={address.street} onChange={e => update('street', e.target.value)} placeholder="Street" />
<input value={address.city} onChange={e => update('city', e.target.value)} placeholder="City" />
{/* ... etc */}
</div>
);
};