Sooner or later you will need to deal with forms in React! Let's check how to deal with things like form data-validations! 👀

In this tutorial, we will cover how to do work with forms and do data-validation in React. Using a library called react-hook-form.

If you are not sure how to use forms with React, check out these 2 basic React forms patterns.

react-hook-form is a library to make dealing with data in forms easy. Data validation is especially easy when using react-hook-form.

Overview:

Let's get into it right away! 💪

With Basic validation

Let's start with how the basic validation may look like. 👇

import React from 'react' ; import useForm from 'react-hook-form' ; const LoginFormWithValidation = ( ) => { const { handleSubmit , register , errors } = useForm ( ) ; const onSubmit = values => { console . log ( values ) ; } ; return ( < form onSubmit = { handleSubmit ( onSubmit ) } > < label htmlFor = "email" > Email : < / label > < input name = "email" type = "email" aria - describedby = "emailError" ref = { register ( { required : 'Email Message Required Message' , pattern : { value : / ^ [ A - Z 0 - 9 . _%+- ] + @ [ A - Z 0 - 9 . - ] + \. [ A - Z ] {2,4} $ / i , message : 'invalid email address' } } ) } / > { errors . email ? ( < span id = "emailError" > { errors . email . message } < / span > ) : ( '' ) } < label htmlFor = "password" > Password : < / label > < input name = "password" type = "password" ref = { register ( { validate : value => value !== 'test123' || 'Too common password, you can do better!' } ) } aria - describedby = "passwordError" / > { errors . password ? ( < span id = "passwordError" > { errors . password . message } < / span > ) : ( '' ) } < input type = "submit" value = "Submit" / > < / form > ) ; } ; export default LoginFormWithValidation ;

At this point, the main focus is the validation. For now, we will be using regular HTML inputs here. 😇

Let see next how we can share the state & validation rules across nested form elements.

Validation For Nested Fields

react-hook-form takes advantage of React Context. It allows you to provide a form context using FormContext. And read the context using a hook called useFormContext.

Since we are talking about the Context API! Check out how to do state management in React using React hooks and the Context API. 🤓

import React from 'react' ; import useForm , { FormContext } from 'react-hook-form' ; import EmailInput from './EmailInput' ; const LoginFormWithNestedInput = ( ) => { const methods = useForm ( ) ; const onSubmit = values => { console . log ( values ) ; } ; return ( < FormContext { ... methods } > < form onSubmit = { methods . handleSubmit ( onSubmit ) } > { } < EmailInput name = "email" > < / EmailInput > < label htmlFor = "password" > Password : < / label > < input name = "password" type = "password" ref = { methods . register ( { validate : value => value !== 'test123' || 'You can do better' } ) } aria - describedby = "passwordError" / > { methods . errors . password ? ( < span id = "passwordError" > { methods . errors . password . message } < / span > ) : ( '' ) } < input type = "submit" value = "Submit" / > < / form > < / FormContext > ) ; } ; export default LoginFormWithNestedInput ;

So far so good, what about the EmailInput component you wonder? 🧐

Here is how it looks like...

import React , { Fragment } from 'react' ; import { useFormContext } from 'react-hook-form' ; const EmailInput = props => { const { register , errors } = useFormContext ( ) ; return ( < Fragment > < label htmlFor = { props . name } > Email : < / label > < input name = { props . name } type = "email" aria - describedby = { ` ${ props . name } -emailError ` } ref = { register ( { required : 'Required' , pattern : { value : / ^ [ A - Z 0 - 9 . _%+- ] + @nordschool . com / i , message : 'Invalid email address - Only Nordschool domain is allowed' } } ) } / > { errors [ props . name ] ? ( < span id = { ` ${ props . name } -emailError ` } > { errors [ props . name ] . message } < / span > ) : ( '' ) } < / Fragment > ) ; } ; export default EmailInput ;

A common use-case for form validations is to use validation schemas.

Validation Schemas

react-hook-form allows declaring validation schemas. Using another validation library called yup we can define validation rules.

Yup is a JS object schema validator and object parser. The API is similar to Joi but smaller and more performant which makes it a good-fit for client-apps.

Using this setup, here is how a simple sign-up form may look like 🙌....

import React from 'react' ; import useForm , { FormContext } from 'react-hook-form' ; import EmailInput from './EmailInput' ; import AddressInputs , { AddressSchema } from './AddressInputs' ; import { string as yupstring , object as yupobject } from 'yup' ; const SignupFormSchema = yupobject ( ) . shape ( { email : yupstring ( ) . required ( 'Email is unfortunately required' ) . email ( 'Please add a real email' ) , name : yupstring ( ) . required ( 'Name is important, what should we call you?' ) , ... AddressSchema } ) ; const SignupForm = ( ) => { const methods = useForm ( { validationSchema : SignupFormSchema } ) ; const onSubmit = values => { console . log ( values ) ; } ; return ( < FormContext { ... methods } > < form onSubmit = { methods . handleSubmit ( onSubmit ) } > < label htmlFor = "name" > Name : < / label > < input name = "name" type = "text" aria - describedby = "nameError" ref = { methods . register ( ) } / > { methods . errors . name ? ( < span id = "nameError" > { methods . errors . name . message } < / span > ) : ( '' ) } < EmailInput name = "email" > < / EmailInput > < AddressInputs name = "email" > < / AddressInputs > < input type = "submit" value = "Search" / > < / form > < / FormContext > ) ; } ; export default SignupForm ;

And the AddressInputs with their custom schema...

import React , { Fragment } from 'react' ; import { useFormContext } from 'react-hook-form' ; import { string as yupstring } from 'yup' ; export const AddressSchema = { streetAddress : yupstring ( ) . required ( 'Street address is required!' ) , postalCode : yupstring ( ) . length ( 4 ) . required ( 'required!' ) , city : yupstring ( ) . required ( 'City is required!' ) } ; const AddressInputs = props => { const { register , errors } = useFormContext ( ) ; return ( < Fragment > < label htmlFor = "stressAddress" > Street Address : < / label > < input name = "streetAddress" type = "text" aria - describedby = "streetAddressError" ref = { register ( ) } / > { errors . streetAddress ? ( < span id = "streetAddressError" > { errors . streetAddress . message } < / span > ) : ( '' ) } < label htmlFor = "postalCode" > Postal Code : < / label > < input name = "postalCode" type = "text" aria - describedby = "postalCodeError" ref = { register ( ) } / > { errors . postalCode ? ( < span id = "postalCodeError" > { errors . postalCode . message } < / span > ) : ( '' ) } < label htmlFor = "city" > City : < / label > < input name = "city" type = "text" aria - describedby = "cityError" ref = { register ( ) } / > { errors . city ? < span id = "cityError" > { errors . city . message } < / span > : '' } < / Fragment > ) ; } ; export default AddressInputs ;

If you noticed we are repeating that input field pattern all over the place! Let's encapsulate the input field elements in an own component. 😎

Custom Input Field

import React , { Fragment } from 'react' ; const InputField = props => { return ( < Fragment > < label htmlFor = { props . name } > { props . label } < / label > < input name = { props . name } type = { props . type || 'text' } aria - describedby = { ` ${ props . name } Error ` } ref = { props . registerFn } / > { props . error ? ( < span id = { ` ${ props . name } Error ` } > { props . error . message } < / span > ) : ( '' ) } < / Fragment > ) ; } ; export default InputField ;

Now our AddressInputs could be refactored to look more like this...

import React , { Fragment } from 'react' ; import { useFormContext } from 'react-hook-form' ; import InputField from './InputField' ; import { string as yupstring } from 'yup' ; export const AddressSchema = { streetAddress : yupstring ( ) . required ( 'Street address is required!' ) , postalCode : yupstring ( ) . length ( 4 ) . required ( 'required!' ) , city : yupstring ( ) . required ( 'City is required!' ) } ; const AddressInputs = props => { const { register , errors } = useFormContext ( ) ; return ( < Fragment > < InputField label = "Street Address:" name = "stressAddress" error = { errors . streetAddress } registerFn = { register ( ) } > < / InputField > < InputField label = "Postal Code:" name = "postalCode" error = { errors . postalCode } registerFn = { register ( ) } > < / InputField > < InputField label = "City:" name = "stressAddress" error = { errors . city } registerFn = { register ( ) } > < / InputField > < / Fragment > ) ; } ; export default AddressInputs ;

Much nicer! 👌

Ok so far so good, now you know enough to get you pretty far!

What about using a component library?

Let have a quick peek on how we can use react-hook-form and material-ui together.

Validation with Material-UI

import React from 'react' ; import useForm from 'react-hook-form' ; import TextField from '@material-ui/core/TextField' ; import Button from '@material-ui/core/Button' ; import { string as yupstring , object as yupobject } from 'yup' ; const ContactFormSchema = yupobject ( ) . shape ( { email : yupstring ( ) . required ( 'Email is required' ) . email ( 'Please enter a valid email' ) , message : yupstring ( ) . required ( 'Please tell us how we can help you' ) , name : yupstring ( ) . required ( 'Name is important, what should we call?' ) } ) ; const ContactForm = ( ) => { const { register , errors , handleSubmit } = useForm ( { validationSchema : ContactFormSchema } ) ; const onSubmit = values => console . log ( values ) ; return ( < form autoComplete = "off" onSubmit = { handleSubmit ( onSubmit ) } noValidate > < TextField id = "name" label = "Name" name = "name" inputRef = { register } placeholder = "Joe" margin = "normal" variant = "outlined" error = { errors . name ? true : false } helperText = { errors . name ? errors . name . message : '' } / > < TextField id = "email" label = "Email" name = "email" inputRef = { register } placeholder = "example@nordschool.com" margin = "normal" variant = "outlined" error = { errors . email ? true : false } helperText = { errors . email ? errors . email . message : '' } / > < TextField required id = "message" multiline rows = "4" name = "message" inputRef = { register } label = "How can we help you today?" placeholder = "Some pizza please!" margin = "normal" variant = "outlined" error = { errors . message ? true : false } helperText = { errors . message ? errors . message . message : '' } / > < Button variant = "contained" type = "submit" > Submit < / Button > < / form > ) ; } ; export default ContactForm ;

That is it, now your basic form-validation training is complete! 🎖️