Inputs & Forms
Input validation

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.

Built-in validation

Textual inputs have these built in validation props: required, maxLength, minLength pattern.

Textual

import {InputText} from 'koval-ui';
export default function Example() {
  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 {InputCheckbox} from 'koval-ui';
export default function Example() {
  return (
    <InputCheckbox
      label="Click to test"
      required />
  );
};

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 4 external validation states: pristine, valid, error and inProgress.

Validation stateExplanation
pristineWhen user didn't interact with input. Empty icon.
validIndicates successfull validation result
errorIndicates failed validation result
inProgressValidation is ongoing. Useful for async validation on server side
import {InputText} from 'koval-ui';
export default function Example() {
  return (
    <InputText validation="error" />
  );
};

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 (opens in a new tab) 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 (opens in a new tab) represents a single value from a set of FormData (opens in a new tab) 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';
const validatorFn = (value) => {
    console.log('Value captured:', value);
    if (value && value.length > 3) {
        return 'Too long';
    } else {
        return '';
    }
}
export default function Example() {
  return (
    <InputText
      placeholder="Type to test"
      validation={validatorFn} />
  );
};

Async validator

Useful for server-side data validation. Runs with a 1000ms debounced delay.

import {InputText} from 'koval-ui';
 
const validatorFn = 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 '';
    }
};
export default function Example() {
    return <InputText placeholder="Type to test" validation={validatorFn} />;
}
Async validation demo

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 '';
};

Use complex validation

It is possible to make 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 {
    InputText,
    InputGroup,
    InputRadio,
    Button
} from 'koval-ui';
const validatorFn = (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 '';
    }
};
export default function Example() {
  return (
      <form action="" style={{
          display: 'flex',
          gap: '12px',
          flexDirection: 'column'
      }}>
          <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" />
        <Button type="submit">Submit</Button>
      </form>
  );
};