Table of Contents
Formik is one of the most popular libraries for building forms. It helps developers do a lot of things with much few lines of code. Some of these things are form state management, validation and error handling. This tutorial will show you three ways in which you can use formik to build React forms.
The pain of building forms
Building React forms may sound easy, but it is not. There are a lot of things involved. Usually the easiest part is to put together the form. Then come the hard parts. There is a form state management and handling values filled in the form. This also includes preparing the initial state of the form.
When you have this, you need functions to handle those fields to keep the form state up-to-date. Next comes validation. You have to ensure all those values are really legit, or valid. This may require some validation logic including regex. As a part of the validation, you also have to ensure you have valid values for all required fields.
What if some value is invalid, or required and missing? When this happens, you have to find out what value is invalid and display correct error message for that field. That’s not the end. You also have to ensure that when the value is valid the error message disappears. Only when you have all this covered you can proceed to submitting the form.
Building React forms with Formik
If this all sounds like a pain to you, you are not alone. For one of these React developer this pain was a motivation to come up with a solution. This solution he came up with was Formik. The idea is to make building React forms easier for developers by doing most of the heavy lifting for them.
This includes the usual things involved in building forms, the form state management, input validation, showing error messages when necessary and also handling form submission. At this moment, there are at least three ways to use Formik to build forms. Below, we will take a look at each of these ways.
Creating simple form validation schema
Formik supports multiple ways to validate forms. One way is to write the validation logic by yourself. Formik will then use that logic and handle error messages. Another option is to use some validation library. In this tutorial, we will choose the second option and use validation library called Yup.
What this library does is it helps you create validation schema for your form. This schema is basically an object that contains validation rules for individual fields in your form. Formik as able to use this schema, built with Yup
, to validate all fields defined in the schema against their specific rules.
Another nice feature of Yup
schema is the option to specify error message for each rule. You do this by passing some text as an argument to rules functions. For example, when field is required, you use required()
function. To specify the error message for this rule you pass the message as an argument: required('This field is required.')
.
All forms we will use in this tutorial will have three fields: name, email and password. All these fields will be required. We will specify all this in the form schema created with Yup
. For the email field, we will also specify that it has to match email format.
import * as Yup from 'yup'
const formSchema = Yup.object().shape({
name: Yup.string().required('First name is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
password: Yup.string().required('Password is required'),
})
Custom forms with Formik
The first option is to use Formik as a wrapper for your React forms. You will create the form and components for all fields you need. This also includes error messages for those fields. Formik will take care of form state, validation and error handling. This option will require only one component provided by Formik called Formik
.
The Formik component
This Formik
component will serve as a wrapper for the whole form. This doesn’t mean it will replace the form
element wrapping your form. If you are using the form
element, it will stay. The Formik
component will wrap this element as well. The Formik
component has a couple of attributes that will be handy.
These attributes are initialValues
, onSubmit
and validationSchema
. The initialValues
attribute specifies object to define initial values of all fields and creates form state. The onSubmit
attribute allows you to specify handler function for onSubmit
event. The validationSchema
attribute allows to specify validation schema to use.
Initial values for all fields, name, email and password, will be empty strings. For now, to handle onSubmit
event, we will now use arrow function and simple console.log()
to log submitted values. Formik
component uses render-prop pattern that helps share code between React component. Don’t worry you don’t need to know how this works.
All you need to know is that Formik
expects its direct children to be a function that returns some React component. In this case, that returned component will be the form
element and its content. Because we will work with custom form elements, we will need to expose some data from Formik
component so we can work with them.
We can get this data by using object destructuring in the function that returns the form
element. The data we will need are values
, errors
, touched
, handleBlur
, handleChange
and handleSubmit
. The values
is an object that contains current values for each form field. We will use this to specify values for input value
attribute.
The errors
is also an object. If there are any errors in the form, invalid or missing fields, you will find it inside this object. The touched
is an object that tells which form field has been touched and which not. Touched field means that someone interacted with that field, it was focused.
The handleBlur
and handleChange
are handlers for inputs onBlur
and onChange
events. These two events will allow Formik track changes in values, update form state, update “touched” status and also run validations when fields lose focus. The handleSubmit
is a handler for form onSubmit
event.
We will use this handler for onSubmit
attribute of form
element to trigger Formik’s handleSubmit
function when the form is submitted.
// Import dependencies:
import { memo } from 'react'
import { Formik } from 'formik'
import * as Yup from 'yup'
// Create form validation schema:
const formSchema = Yup.object().shape({
name: Yup.string().required('First name is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
password: Yup.string().required('Password is required'),
})
// Create the form component:
export const FormCustom = memo(() => {
return (
<Formik
initialValues={{ name: '', email: '', password: '' }}
onSubmit={(values) => {
console.log(values)
}}
validationSchema={formSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
}) => <form></form>}
</Formik>
)
})
FormCustom.displayName = 'FormCustom'
The form content
The next step is putting together the content of the form, individual fields. This will be quick and easy. Each field will be composed of div
element used as a wrapper. Inside this wrapper will be label
and input
elements. There will be also p
element with an error message. To make sure everything works we will need two things.
First, we will need to use correct value for input name
attributes. Formik uses this attribute, or id
, to connect each field with correct property in the form state. We set the initial values object to have properties name
, email
and password
. This means that we will have to use the same values for each name
attribute, or id
, or both.
The second thing are the onChange
and onBlur
input events handler functions. We need to connect Formik state with each input. This will allow Formik to track changes of values and blur events and update values
, touched
and errors
accordingly. Last thing are the value
input attributes for each input field.
Those inputs should be controlled by Formik state. This will allow to display current values in Formik state as values of corresponding input fields. To do this, we will use values
object and its specific property to get latest correct value for each input field.
// ... Previous code
export const FormCustom = memo(() => {
return (
<Formik
initialValues={{ name: '', email: '', password: '' }}
onSubmit={(values) => {
console.log(values)
}}
validationSchema={formSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
}) => (
<form onSubmit={handleSubmit} noValidate>
<div>
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
/>
</div>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
/>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
)}
</Formik>
)
})
FormCustom.displayName = 'FormCustom'
Simple error messages
We have Formik and form state. We also have form content. The last thing that remains are error messages. One part of this is already covered by validation schema we created with Yup
and Formik. The second part are error messages. We have to tell Formik where we want to show them and under what condition. Both these things will be easy.
To address the first, we will put each error message under corresponding field. We will use p
elements to show the text we defined for each rule in our Yup
validation schema. The condition for when to display each message will following: We want to show the error when field is empty or invalid, but only after it has been touched.
This will help prevent the errors popping up when someone just opens the form. And, don’t worry. Formik automatically sets all fields to touched when the form is submitted. So, if someone tries to submit empty form, error messages for invalid fields will correctly pop up because Formik will set all fields as touched and there are some errors.
Since we specified error messages in validation schema we only have to ensure that Formik displays correct error message for each field. We will do this by using the errors
object and correct property (field name). We will use the same property with touched
object to check if specific field has been touched.
// ... Previous code
export const FormCustom = memo(() => {
return (
<Formik
initialValues={{ name: '', email: '', password: '' }}
onSubmit={(values) => {
console.log(values)
}}
validationSchema={formSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
}) => (
<form onSubmit={handleSubmit} noValidate>
<div>
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
/>
{/* Add error message for "Name" field */}
{errors.name && touched.name && <p>{errors.name}</p>}
</div>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
{/* Add error message for "Email" field */}
{errors.email && touched.email && <p>{errors.email}</p>}
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
/>
{/* Add error message for "Password" field */}
{errors.password && touched.password && <p>{errors.password}</p>}
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
)}
</Formik>
)
})
FormCustom.displayName = 'FormCustom'
Putting it together
Since Formik, form content and error messages are done your custom React form is complete as well. Below is the complete code for the whole form. The last thing that needs some work is what to do when the form is submitted. You handle this in Formik’s onSubmit
attribute and its handler function. Replace that console.log()
with anything you need.
// Import dependencies:
import { memo } from 'react'
import { Formik } from 'formik'
import * as Yup from 'yup'
// Create form validation schema:
const formSchema = Yup.object().shape({
name: Yup.string().required('First name is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
password: Yup.string().required('Password is required'),
})
// Create the form component:
export const FormCustom = memo(() => {
return (
<Formik
initialValues={{ name: '', email: '', password: '' }}
onSubmit={(values) => {
console.log(values)
}}
validationSchema={formSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
}) => (
<form onSubmit={handleSubmit} noValidate>
<div>
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
/>
{errors.name && touched.name && <p>{errors.name}</p>}
</div>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
{errors.email && touched.email && <p>{errors.email}</p>}
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
/>
{errors.password && touched.password && <p>{errors.password}</p>}
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
)}
</Formik>
)
})
FormCustom.displayName = 'FormCustom'
Conclusion: 3 Ways to build React forms with Formik pt.1
Formik library makes it much easier to build and work with React forms. This tutorial showed you how to make the Formik
component to work with custom HTML elements. This can help you use Formik
to handle only things such as state management and validation while letting you do the rest as you want.
If you liked this article, please subscribe so you don't miss any future post.
If you'd like to support me and this blog, you can become a patron, or you can buy me a coffee 🙂