Skip to content

useForm

React hooks for form validation

useForm: Function

useForm is custom hook for managing forms with ease. It takes optional arguments. The following example demonstrates all of the arguments along with their default values.

const { register } = useForm({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
})type FormInputs = {
  firstName: string;
  lastName: string;
};

const { register } = useForm<FormInputs>({
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: {},
  resolver: undefined,
  context: undefined,
  criteriaMode: "firstError",
  shouldFocusError: true,
})
mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit'React Native: compatible with Controller
NameTypeDescription
onSubmitstringValidation will trigger on the submit event and invalid inputs will attach onChange event listeners to re-validate them.
onBlurstringValidation will trigger on the blur event.
onChangestringValidation will trigger on the change event with each input, and lead to multiple re-renders. Warning: this often comes with a significant impact on performance.
onTouchedstring

Validation will trigger on the first blur event. After that, it will trigger on every change event.

Note: when using with Controller, make sure to wire up onBlur with the render prop.

allstringValidation will trigger on the blur and change events.
reValidateMode: onChange | onBlur | onSubmit = 'onChange'React Native: Custom register or using Controller

This option allows you to configure when inputs with errors get re-validated after submit. By default, validation is only triggered during an input change.

defaultValues: Record<string, any> = {}

The defaultValues for inputs are used as the initial value when a component is first rendered, before a user interacts with it. It is encouraged that you set defaultValues for all inputs to non-undefined values such as the empty string or null.

You can set an input's default value with defaultValue/defaultChecked (read more from the React doc for Default Values). You can pass defaultValues as an optional argument to useForm() to populate the default values for the entire form, or set values on an individual Controller component via its defaultValue property.

Rules

  • defaultValues are cached on the first render within the custom hook. If you want to reset the defaultValues, you should use the reset api.

  • defaultValues will be injected into watch, useWatch, Controller and useController's defaultValue.

  • It's not default for the form, to include additional form values. To do so:

    1. Register hidden inputs. For example: <input type="hidden" {...register('test'} />

    2. Combine values via the onSubmit callback.

    3. Register an input with a value. For example: register('test')({ test: "test"})

  • defaultValues will be shallowly merged with form submission data.

const { register } = useForm({
  defaultValues: {
    firstName: "bill",
    lastName: "luo",
  }
})

<input {...register("firstName")} /> // ✅ working version
<Controller name="lastName" render={({ field }) => <input {...field} />} />  // ✅ working version
type Inputs = {
  firstName: string;
  lastName: string;
  email: string;
  isDeveloper: boolean;
}
  
const { register } = useForm<Inputs>({
  defaultValues: {
    firstName: "bill",
    lastName: "luo",
    email: "bluebill1049@hotmail.com",
    isDeveloper: true
  }
})

<input {...register("firstName")} /> // ✅ working version

context:
object

This context object is mutable and will be injected into the resolver's second argument or Yup validation's context object.


CodeSandbox

criteriaMode
firstError | all

  • When set to firstError (default), only the first error from each field will be gathered.

  • When set to all, all errors from each field will be gathered.

CodeSandbox

shouldFocusError:
boolean = true

When set to true (default) and the user submits a form that fails the validation, it will set focus on the first field with an error.

Note: only registered fields with a ref will work. Custom registered inputs do not apply. For example: register('test') // doesn't work

Note: the focus order is based on the register order.


resolver: (values: any, context?: object, options: Object) => Promise<ResolverResult> | ResolverResult

This function allows you to use any external validation library such as Yup, Zod, Joi, Superstruct, Vest and many others. The goal is to make sure you can seamlessly integrate whichever validation library you prefer. If you're not using a library, you can always write your own logic to validate your forms.

npm install @hookform/resolvers

Rules

  • Make sure you are returning an object that has both a values and an errors property. Their default values should be an empty object. For example: {}.

  • The keys of the error object should match the name values of your fields.

  • This function will be cached, while context is a mutable object that can be changed on each re-render.

  • Re-validation of an input will only occur one field at time during a user’s interaction. The lib itself will evaluate the error object to trigger a re-render accordingly.

  • A resolver cannot be used with the built-in validators (e.g.: required, min, etc.)

import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from "yup";

const schema = yup.object().shape({
  name: yup.string().required(),
  age: yup.number().required(),
});

const App = () => {
  const { register, handleSubmit } = useForm({
    resolver: yupResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input {...register("name")} />
      <input type="number" {...register("age")} />
      <input type="submit" />
    </form>
  );
};import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from "yup";

type Inputs = {
  name: string;
  age: string;
};

const schema = yup.object().shape({
  name: yup.string().required(),
  age: yup.number().required(),
});

const App = () => {
  const { register, handleSubmit } = useForm<Inputs>({
    resolver: yupResolver(schema), // yup, joi and even your own.
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input {...register("name")} />
      <input type="number" {...register("age")} />
      <input type="submit" />
    </form>
  );
};
import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";

const schema = z.object({
  name: z.number(),
  age: z.number()
});

const App = () => {
  const { register, handleSubmit } = useForm({
    resolver: zodResolver(schema)
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d)}>
      <input {...register("name")} />
      <input {...register("age")} type="number" />
      <input type="submit" />
    </form>
  );
};

export default App;
import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";

const schema = z.object({
  name: z.number(),
  age: z.number()
});

type Schema = z.infer<typeof schema>;

const App = () => {
  const { register, handleSubmit } = useForm<Schema>({
    resolver: zodResolver(schema)
  });

  const onSubmit = (data: IFormInput) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("name")} />
      <input {...register("age")} type="number" />
      <input type="submit" />
    </form>
  );
};

export default App;
import React from 'react';
import { useForm } from 'react-hook-form';
import { joiResolver } from '@hookform/resolvers/joi';
import Joi from "joi";

const schema = Joi.object({
  name: Joi.string().required(),
  age: Joi.string().required(),
});

const App = () => {
  const { register, handleSubmit } = useForm({
    resolver: joiResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d)}>
      <input {...register("name")} />
      <input type="number" {...register("age")} />
      <input type="submit" />
    </form>
  );
};
import React from "react";
import { useForm } from "react-hook-form";
import { joiResolver } from "@hookform/resolvers/joi";
import Joi from "joi";

interface IFormInput {
  name: string;
  age: number;
}

const schema = Joi.object({
  name: Joi.string().required(),
  age: Joi.number().required()
});

const App = () => {
  const { register, handleSubmit, errors } = useForm<IFormInput>({
    resolver: joiResolver(schema)
  });
  const onSubmit = (data: IFormInput) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("name"} />
      <input type="number" {...register("age"} />
      <input type="submit" />
    </form>
  );
}
import React from 'react';
import { useForm } from 'react-hook-form';
import { superstructResolver } from '@hookform/resolvers/superstruct';
import { struct } from 'superstruct';

const schema = struct({
  name: 'string',
  age: 'number',
});

const App = () => {
  const { register, handleSubmit } = useForm({
    resolver: superstructResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input {...register("name")} />
      <input {...register("age")} type="number" />
      <input type="submit" />
    </form>
  );
};
import * as React from 'react';
import { useForm } from 'react-hook-form';
import { vestResolver } from '@hookform/resolvers/vest';
import vest, { test, enforce } from 'vest';

const validationSuite = vest.create((data = {}) => {
  test('username', 'Username is required', () => {
    enforce(data.username).isNotEmpty();
  });

  test('username', 'Must be longer than 3 chars', () => {
    enforce(data.username).longerThan(3);
  });

  test('password', 'Password is required', () => {
    enforce(data.password).isNotEmpty();
  });

  test('password', 'Password must be at least 5 chars', () => {
    enforce(data.password).longerThanOrEquals(5);
  });

  test('password', 'Password must contain a digit', () => {
    enforce(data.password).matches(/[0-9]/);
  });

  test('password', 'Password must contain a symbol', () => {
    enforce(data.password).matches(/[^A-Za-z0-9]/);
  });
});

const App = () => {
  const { register, handleSubmit } = useForm({
    resolver: vestResolver(validationSuite),
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register("username")} />
      <input {...register("password")} />
      <input type="submit" />
    </form>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";
import * as Joi from "joi";

const validationSchema = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required()
});

const App = () => {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: async data => {
      const { error, value: values } = validationSchema.validate(data, {
        abortEarly: false
      });

      return {
        values: error ? {} : values,
        errors: error
          ? error.details.reduce((previous, currentError) => {
              return {
                ...previous,
                [currentError.path[0]]: currentError
              };
            }, {})
          : {}
      };
    }
  });

  const onSubmit = data => {
    console.log(data)
  };

  return (
    <div className="App">
      <h1>resolver</h1>
      
      <form onSubmit={handleSubmit(onSubmit)}>
        <label>Username</label>
        <input {...register("username")} />
        {errors.username && <p>errors.username.message</p>}
        <input type="submit" />
      </form>
    </div>
  );
};
import * as React from "react";
import { useForm } from "react-hook-form";
import * as Joi from "joi";

interface IFormInputs {
  username: string;
}

const validationSchema = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required()
});

const App = () => {
  const { register, handleSubmit, errors } = useForm<IFormInputs>({
    resolver: async data => {
      const { error, value: values } = validationSchema.validate(data, {
        abortEarly: false
      });

      return {
        values: error ? {} : values,
        errors: error
          ? error.details.reduce((previous, currentError) => {
              return {
                ...previous,
                [currentError.path[0]]: currentError
              };
            }, {})
          : {}
      };
    }
  });

  const onSubmit = (data: IFormInputs) => console.log(data);

  return (
    <div className="App">
      <h1>resolver</h1>

      <form onSubmit={handleSubmit(onSubmit)}>
        <label>Username</label>
        <input {...register("username")} />
        {errors.username && <p>errors.username.message</p>}
        <input type="submit" />
      </form>
    </div>
  );
};
Edit