⚙️ Input validation
Input validation in Koval is made through built-in validation properties (required, pattern, maxLength, minLength, max, min) or a validation state (external validation result or callback function prop), which supports custom and asynchronous validation.
Koval leverages the native HTMLInputElement error API and browser-integrated error tooltips.
Basic validation
Textual
Textual inputs have required, maxLength, minLength pattern validation props same as HTMLInputElement.
//import {InputText} from 'koval-ui'; function Example(props) { return ( <InputText maxLength={6} placeholder="Type to test" // email address format expected pattern="[^@\\s]+@[^@\\s]+" // show error when input is empty required /> ); }
Interactive
Interactive inputs support only required built in validation.
//import {InputText} from 'koval-ui'; function Example(props) { return ( <div style={{width: 188}}> <InputCheckbox label="Click to test" required /> </div> ); }
Custom validation
Developers can provide a validation prop to each input for custom validation. This prop functions as an external state or as a callback, executing each time the input value changes.
External (aka controlled) validation
Don't use internal input validation (pattern etc) when validation is set to controlled mode.
validation props can accept four external validation states: pristine, valid, error and inProgress.
| Validation state | Explanation |
|---|---|
| pristine | When user didn't interact with input. Empty icon. |
| valid | Indicates successfull validation result |
| error | Indicates failed validation result |
| inProgress | Validation is ongoing. Useful for async validation on server side |
//import {InputText} from 'koval-ui'; function Example(props) { return ( <div style={{width: 188}}> <InputText validation="error" /> </div> ); }
Custom validator function
Internal input validation (pattern, etc.) works normally together with validator function.
type validatorFn = (
value: unknown,
validityState: ValidityState,
formState: Record<string, FormDataEntryValue>
) => string | Promise<string>;
The ValidityState interface represents the browser built-in validity states for an input.
formState is a special object containing full form state, applicable if the input is part of a form. FormDataEntryValue represents a single value from a set of FormData key-value pairs.
Validator can work in either sync (returns string) or (returns Promise<string>) async mode. An empty string '' indicates a successful validation result, while a non-empty string produces an input error with the provided text.
Sync validator
Ideal for complex client-side data validations.
//import {InputText} from 'koval-ui'; function Example(props) { const validatorFn = useCallback(value => { console.log('Value captured:', value); if (value && value.length > 3) { return 'Too long'; } else { return ''; } }, []); return ( <div style={{width: 188}}> <InputText placeholder="Type to test" validation={validatorFn} /> </div> ); }
Async validator
Useful for server-side data validation. Runs with a 1000 ms debounced delay.
//import {InputText} from 'koval-ui'; function Example(props) { const validatorFn = useCallback(async value => { console.log('Value captured:', value); await new Promise(resolve => setTimeout(resolve, 1000)); if (value && value.length > 3) { return `Last captured: ${value}`; } else { return ''; } }, []); return ( <div style={{width: 188}}> <InputText placeholder="Type to test" validation={validatorFn} /> </div> ); }
Override built-in error messages
Custom error messages can be displayed for built-in validations.
const validatorFn = (value: unknown, validityState: ValidityState) => {
if (validityState.valueMissing) {
return 'Please provide value for the input';
} else if (validityState.patternMismatch) {
return 'Please provide valid email';
}
return '';
};
Implement complex validation
It is possible to make an input validation result to be dependent on other field value belonging to the same form. Enable revalidateOnFormChange prop and use formState parameter inside validatorFn.
//import {Form, InputText, InputGroup, InputRadio, Button} from 'koval-ui'; function Example(props) { const validatorFn = useCallback((value, validityState, formState) => { switch (formState['case-selector']) { case 'lowercase': { const isLowerCase = value.toLowerCase() === value; return isLowerCase ? '' : 'Only lower case allowed.'; } case 'uppercase': { const isUpperCase = value.toUpperCase() === value; return isUpperCase ? '' : 'Only upper case allowed.'; } default: return ''; } }, []); return ( <Form> <InputGroup name="case-selector"> <InputRadio defaultChecked={true} label="Allow uppercase" value="uppercase" /> <InputRadio label="Allow lowercase" value="lowercase" /> </InputGroup> <InputText revalidateOnFormChange validation={validatorFn} name="text" placeholder="Validated dynamically" /> <div> <Button type="submit">Submit</Button> </div> </Form> ); }