Validation

The useForm function can also accept a validation function. It's a function that accepts the current form values and needs to return an object with the same shape, but instead of the property values it can have a string or array of strings containing the validation messages.

const { form } = useForm({
  // ...
  validate: (values) => {
    const errors = {}
    if (!values.email || !/^[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/.test(values.email)) {
      errors.email = 'Must be a valid email';
    }
    if (!values.password) errors.password = [
      'Must not be empty',
      'Must be over 8 characters',
    ];
    if (values.password && values.password.length < 8) {
      errors.password = ['Must be over 8 characters'];
    }
    return errors;
  },
  // ...
});

It shouldn't be a hard task to use a third party library, as long as you transform their result into something that Felte understands.

Note that the validate function can be asynchronous.

Felte will validate whichever field it considers as touched as you fill the form, and it will validate all fields (and set them as touched) when submitting it.

Server errors

You can add an onError function to the useForm configuration that will be called if the onSubmit function throws an error. It will be called with the value thrown from the onSubmit function, or with an instance of FelteSubmitError if using Felte's default submit handler. You can use the onError function to shape your server errors into the same shape that the validate function expects and return them from the same function for them to be handled.

const { form } = useForm({
  // ...
  // Assuming your server already returns them with the appropriate shape.
  // Might not be the case for most cases.
  onError: (errors) => errors,
  // ...
});

Error handling

useForm returns also its errors and touched fields as accessors.

import { useForm } from '@felte/react';

export function Form() {
  const { form, errors, touched } = useForm({ \*...*\ });

  return (
    <>
      {/* ... */}
      <pre>
        {/* Prettify the errors and display them in HTML */}
        {JSON.stringify(errors(), null, 2)}
      </pre>
      <pre>
        {JSON.stringify(touched(), null, 2)}
      </pre>
      {/* ... */}
    </>
  );
}

You can read more above them in the stores section.

You don't need to manually handle this errors. Felte provides four official packages to handle your errors for you, either using Tippy, directly mutating the DOM, providing a React component or using the browser's built-in constraint validation API. You can read more about this in the reporters section.

Multiple validations

The validate property of the configuration object can also be an array of validation functions. The resulting errors from running each validation function will be merged into a single object. If a single property was assigned two or more errors from the validation functions, the property will be an array of strings. This might not be useful for common scenarios, but it will come useful when using any of our validator packages.

Warnings

Sometimes you may want to display certain validation messages that do not prevent the form from submitting, such as a password security message. Felte provides a store called warnings for this, and such warnings can be added to the store by using the warn property on useForm's configuration.

const { form } = useForm({
  // ...
  warn: (values) => {
    const warnings = {}
    if (values.password && values.password.length < 8) {
      warnings.password = 'Your password could be more secure';
    }
    return warnings;
  },
  // ...
});

The warn function works exactly the same as the validate function. With the same features and constraints. Multiple warn functions can be passed as an array, and the shape of warnings can contain either a string or an array of strings.

Async validations

Felte supports asynchronous validations by default by returning a promise from your validation functions, either for warnings or errors. Felte takes care of possible race conditions by ignoring the results of previous async validations if it's executed again before the previous one finishes.

const { form } = useForm({
  // ...
  validate: async (values) => {
    const response = await someAsyncAction(values);
    // Do something with the response and return the errors
  },
  // ...
});

Validations execute at every keypress, so make sure to not execute anything you don't want to spam. For these kinds of actions, Felte also supports for your validations to be debounced.

Debounced validations

If your validation shouldn't be called for every keypress of the user, such as an expensive synchronous validation or an API call, Felte can debounce said validations for you. These validations should be added within the debounced property of Felte's configuration:

const { form } = useForm({
  // ...
  debounced: {
    // defaults to 300
    timeout: 300,
    validate: async (values) => {
      const response = await someFetchCall(values);
      // Do something with the response and return the errors
    },
    warn: async (values) => {
      const response = await someOtherFetchCall(values);
      // Do something with the response and return the warnings
    },
  },
  // ...
});

The debounced object accepts the following properties: