Installation
Installing React Hook Form only takes a single command and you're ready to roll.
npm install react-hook-formExample
The following code will demonstrate the basic usage:
import React from 'react'
import { useForm } from 'react-hook-form'
export default function App() {
const { register, handleSubmit, watch, errors } = useForm()
const onSubmit = data => { console.log(data) }
console.log(watch('example')) // watch input value by passing the name of it
return (
{/* "handleSubmit" will validate your inputs before invoking "onSubmit" */}
<form onSubmit={handleSubmit(onSubmit)}>
{/* register your input into the hook by invoking the "register" function */}
<input name="example" defaultValue="test" ref={register} />
{/* include validation with required or other standard HTML validation rules */}
<input name="exampleRequired" ref={register({ required: true })} />
{/* errors will return when field validation fails */}
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
)
}
♦
Video Tutorial
In this video tutorial, I have demonstrated the basic usage and concept of using React Hook Form.
Register fields
One of the key concepts for React Hook Form is to register
your uncontrolled component into the Hook and hence enabling its value to be validated and gathered for submitting.
Note: Each field is required to have a unique name
as a key for the registration process.
Note: React Native will need to use a manual register command: register({ name: 'test' }, { required: true })
or using Controller to wrap and auto register your component. You can also read more at React Native section.
import React from 'react'
import { useForm } from 'react-hook-form'
export default function App() {
const { register, handleSubmit } = useForm()
const onSubmit = data => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input name="firstName" ref={register} />
<select name="gender" ref={register}>
<option value="male">male</option>
<option value="female">female</option>
</select>
<input type="submit" />
</form>
);
}
Apply validation
React Hook Form make form validation easy by aligning with existing HTML standard form validation.
List of validation rules supported by:
- required
- min
- max
- minLength
- maxLength
- pattern
- validate
You can read more detail on each rule at the register section.
import React from 'react'
import { useForm } from 'react-hook-form'
export default function App() {
const { register, handleSubmit } = useForm()
const onSubmit = data => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input name="firstName" ref={register({ required: true, maxlength: 20 })} />
<input name="lastName" ref={register({ pattern: /^[A-Za-z]+$/i })} />
<input name="age" type="number" ref={register({ min: 18, max: 99 })} />
<input type="submit" />
</form>
);
}
Adapting existing form
Working on an existing form is simple. The important step is to applyregister
into existing component's ref
.import React from 'react'
import { useForm } from 'react-hook-form'
// The following component is an example of your existing Input Component
const Input = ({ label, register, required }) => (
<>
<label>{label}</label>
<input name={label} ref={register({ required })} />
</>
)
// you can use React.forwardRef to pass the ref too
const Select = React.forwardRef(({ label, register }, ref) => (
<>
<label>{label}</label>
<select name={label} ref={ref}>
<option value="20">20</option>
<option value="30">30</option>
</select>
</>
))
export default function App() {
const { register, handleSubmit } = useForm()
const onSubmit = data => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input label="First Name" register={register} required />
<Select label="Age" ref={register} />
<input type="submit" />
</form>
)
}
Work with UI library
React Hook Form has made it easy to integrate with external UI component libraries.
Option 1: The best way is to check if the component you wish to use exposes an innerRef
or ref
that can be used to register
. For example: Material-UI's TextField
accepts inputRef
as one of it's props. Simply pass register
to it.
<TextField inputRef={register} label="First name" name="FirstName"/>
Option 2: Sometimes components don't expose a prop to register, for example react-select
or react-datepicker
.
The next easiest way is to use the Controller wrapper component, which will take care of the custom register process for you.
import React from "react";
import { useForm, Controller } from "react-hook-form";
import Select from "react-select";
import Input from "@material-ui/core/Input";
import { Input as InputField } from "antd";
export default function App() {
const { control, handleSubmit } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller as={<Input />} name="HelloWorld" control={control} defaultValue="" />
<Controller as={<InputField />} name="AntdInput" control={control} defaultValue="" />
<Controller
as={<Select />}
name="reactSelect"
control={control}
onChange={([selected]) => {
// React Select return object instead of value for selection
return { value: selected };
}}
defaultValue={{}}
/>
<input type="submit" />
</form>
);
}
Option 3: Lastly we can set up a custom register using the useEffect Hook and update the value via setValue
.
import React from 'react';
import { useForm } from 'react-hook-form';
import Select from "react-select";
import Input from "@material-ui/core/Input";
import { Input as InputField } from 'antd';
export default function App() {
const { register, handleSubmit, setValue } = useForm();
const onSubmit = data => console.log(data);
const [values, setReactSelectValue] = useState({ selectedOption: [] });
const handleMultiChange = selectedOption => {
setValue("reactSelect", selectedOption);
setReactSelectValue({ selectedOption });
}
const handleChange = (e) => {
setValue("AntdInput", e.target.value);
}
React.useEffect(() => {
register({ name: "reactSelect" }); // custom register react-select
register({ name: "AntdInput" }); // custom register antd input
}, [register])
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input name="HelloWorld" inputRef={register} />
<InputField name="AntdInput" onChange={handleChange} />
<Select
value={values.selectedOption}
options={options}
onChange={handleMultiChange}
isMulti
/>
<input type="submit" />
</form>
);
}
Controlled Input
React Hook Form embrace uncontrolled components and native HTML inputs, however it's hard to avoid working with external controlled component such as React-Select, AntD and Material-UI, hence we have built a wrapper component: Controller to streamline the integration process while still giving you the freedom to use custom register with your needs.
Every props you pass to Controller component, will be forwarded to the Component instance you provided with the as
prop. That means imagine you have a custom Switch
component that require a label
prop. You can pass this prop to the Controller component directly. The name
prop will be used mainly to access the value through the form later.
If you use a defaultValue
prop, it will take priority over the useForm
defaultValues
value for your property given at name
prop.
import React from "react";
import { useForm, Controller } from "react-hook-form";
import ReactSelect from "react-select";
import { TextField, Checkbox } from "@material-ui/core";
function App() {
const methods = useForm();
const { handleSubmit, control, reset } = methods;
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{* // Preferred syntax on most cases. If you need props, pass TextField props to Controller props (forwarded) *}
<Controller as={TextField} name="TextField" control={control} defaultValue="" />
{* // Another possibility, any potential props passed to <Checkbox/> will be overrided. SomeName => Checkbox *}
<Controller
as={<Checkbox name="SomeName"/>}
name="Checkbox"
value="test"
control={control}
defaultValue={false}
/>
<button>Submit</button>
</form>
);
}
Integrate global state
React Hook Form doesn't require you to have a state management to store your data, but you can easily integrate with one.import React from 'react'
import { useForm } from 'react-hook-form'
import { connect } from 'react-redux'
import updateAction from './actions'
export default function App(props) {
const { register, handleSubmit, setValue } = useForm()
// Submit your data into Redux store
const onSubmit = (data) => props.updateAction(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input name="firstName" defaultValue={props.firstName} ref={register} />
<Input name="lastName" defaultValue={props.lastName} ref={register} />
<input type="submit" />
</form>
);
}
// Connect your component with redux
connect(({ firstName, lastName }) => ({ firstName, lastName }), updateAction)(YourForm)
Handle errors
React Hook Form provides anerrors
object to show you the errors within the form.import React from 'react'
import { useForm } from 'react-hook-form'
export default function App() {
const { register, errors, handleSubmit } = useForm()
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input name="firstName" ref={register({ required: true })} />
{errors.firstName && 'First name is required'}
<Input name="lastName" ref={register({ required: true })} />
{errors.lastName && 'Last name is required'}
<input type="submit" />
</form>
);
}
React Native
You will get the same performance enhancement from an Uncontrolled Component. However, there are certain APIs which are not compatible with React Native (duo to the API difference from web and native). You will have to use a manual register as shown in the following example.
import React from "react";
import { Text, View, TextInput, Button, Alert } from "react-native";
import { useForm, Controller } from "react-hook-form";
export default function App() {
const { control, handleSubmit, errors } = useForm();
const onSubmit = data => Alert.alert("Form Data", data);
const onChange = args => {
return {
value: args[0].nativeEvent.text,
};
};
return (
<View>
<Text>First name</Text>
<Controller
as={<TextInput />}
control={control}
name="firstName"
onChange={onChange}
rules={{ required: true }}
defaultValue=""
/>
{errors.firstName && <Text>This is required.</Text>}
<Text>Last name</Text>
<Controller as={<TextInput />} control={control} name="lastName" defaultValue="" />
<Button onPress={handleSubmit(onSubmit)} />
</View>
);
}
TypeScript
React Hook Form is built with Typescript
, so you can define a FormData
type to support form values.
import * as React from "react";
import { useForm } from "react-hook-form";
type FormData = {
firstName: string;
lastName: string;
};
export default function App() {
const { register, setValue, handleSubmit, errors } = useForm<FormData>();
const onSubmit = handleSubmit(({ firstName, lastName }) => {
console.log(firstName, lastName);
}); // firstName and lastName will have correct type
return (
<form onSubmit={onSubmit}>
<label>First Name</label>
<input name="firstName" ref={register} />
<label>Last Name</label>
<input name="lastName" ref={register} />
<button
type="button"
onClick={() => {
setValue("lastName", "luo"); // ✅
setValue("firstName", true); // ❌: true is not string
errors.bill; // ❌: property bill does not exist
}}
>
SetValue
</button>
</form>
);
}
Want to learn more?
Check out the React Hook Form documentation and learn all about the API.