Building React forms can be lengthy and painful process. Not anymore with library called Formik. Formik provides a nice and easy way to build React forms. This tutorial will show you how to build React forms with HTML elements along with the useFormik()
hook provided by Formik.
You can find this example on Codesandbox.
A quick intro
In the first part of this tutorial, we took a look at how to build React forms with custom HTML elements and form state and logic provided by Formik. In the second part, we switched to Formik components and build form with them. In this final part we will make another change, a small step back.
We will switch from Formik components back to HTML form elements. This also includes dropping the Form
component provided by Formik we used as a wrapper in previous parts. We will replace this component with HTML form
element. We will also use HTML form components for the rest of form content.
Removing Formik components also removes all functionality provided by Formik. However, this doesn't mean we can't use Formik to build React forms. Formik allows us to use its functionality through hooks. This is what we will do in this tutorial. We will build React form and connect it to Formik through useFormik()
hook.
Formik and context
Switching to useFormik()
hook can make things easier in some situations. However, there is a downside. When we remove the Form
component we also remove Formik's context automatically created when we use this component. This means that we can no longer use the useFormikContext hook to access this context and Formik's methods.
If you are not using this specific hook, there is no reason to worry about anything. You can use Formik with useFormik()
hook to build React forms just as with Form
and useFormikContext()
.
Project dependencies
Let's quickly talk about dependencies. This tutorial will use only few of them. We will need some dependencies required to run our React app. These will be react
, react-dom
and react-scripts
. These three will all be version 17.0.2
. Next is the Formik
library. This will be version 2.2.9
.
The last dependency will be validation library called Yup
. We will use this library to create a simple validation schema for our React form. The version of Yup will be 0.32.9
. This is all we need.
Validation schema
Validation schema for the form we are going to build will be simple. We will need to validate only three form fields, name, email and password. All these fields will be required and their values will be strings. In case of email field, we will also want to ensure the value if in email format.
Thanks to Yup library, building this schema is quick and easy. All we have to do is to create new Yup object and define its shape. The shape is in the form of an object where each key
or property is name of one field. The value is a validation rule for that field. This validation rule is composed of method provided by Yup.
For example, all field values must be strings and are required. We can create this rule by using Yup's string()
and required()
methods. We can also specify error message for each of these validation methods by passing the message as an argument to the specific method, required('Field is required')
.
To make the schema complete we need to add one more rule, for email.
Here, we will use Yup's email()
method. This method will check that a specific field value is provided in email format. If not, Yup will detect an error. This is all we need for our validation schema.
// Import Yup:
import * as Yup from 'yup'
// Create validation schema for form
// with three fields: "name", "email" and "password":
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'),
})
Using useFormik() hook
As we discussed in the introduction part, we are going to build the React form with Formik's useFormik()
hook. This hook allows us to do a couple of things. First, we can use it to setup Formik state for the form. This means specifying what fields we will need and their initial values.
This hook also allows us to specify any custom validation schema we want to use for our React form. For example, the schema we've built with Yup. We can also specify how we want to handle the onSubmit event form will fire when submitted. We can setup this through config object the useFormik()
hook accepts as a parameter.
This is the input. Next is the output. The useFormik()
hook returns methods and states. We can use these methods as input event handlers to connect individual form fields to Formik and its states. So, when field is focused and receives some input Formik will be able to register it and store that input inside its state.
Receiving and storing input is not enough. We also need a way to access these stored values. Otherwise, we can't render them in form fields as input values. This is not a problem. One of the states the useFormik()
hook exposes is values
state. This state contains values for all registered form fields.
We can use this state in combination with specific field name to receive value specific field should display. Two additional states we will use are errors
and touched
states. When any field in the form contains error, created by Yup validation, that error will end up in Formik's errors
state.
The touched
states registers if specific field has been touched, focused, since the form was rendered. We will use these two states to decide when to show error messages for fields with errors.
// Import dependencies:
import { memo } from 'react'
import { useFormik } 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 FormHook = memo(() => {
// Call useFormik() hook with config
// and assign it to formik variable:
const formik = useFormik({
// Initial values for each form field:
initialValues: {
name: '',
email: '',
password: ''
},
onSubmit: (values) => {
// Logic to handle onSubmit event
console.log(values)
},
// Schema for validating the form:
validationSchema: formSchema
})
return (/* Form component */)
})
FormHook.displayName = 'FormHook'
Building form inputs
The markup for each field will be simple. There will be label
, input
and p
for error message. These elements will be wrapped inside a div
element. Each input element will have type
, name
, value
, onChange
and onBlur
attributes. Type will always be "text"
. Name will correspond to each field name.
To get the value for value
attribute we will reference the values
state returned by useFormik()
hook, along with the field name. Note: we assigned all values returned by this hook to the formik
variable. So, to access this state, we have to reference this variable.
We will do this also to access Formik's event handlers. Specifically handlers for onChange
and onBlur
events. These will be Formik's handleChange
and handleBlur
methods. The handleChange
will ensure our form state is always up-to-date. The handleBlur
will help us register if field has been touched.
The last piece are error messages. We've already defined the messages itself in the validation schema in the beginning. Now, we need to make sure they are visible at the right time. We can ensure this by checking two things. First, the field contains an error. Second, the field has been touched.
Only if these two conditions are true we want the error message to appear. To display the error message itself, we can use the errors
state along with name of the specific field. If there is an error for this field, this will return the message as a string. We will get all this data from props.
import { memo } from 'react'
export const Input = memo((props) => (
<div>
<label htmlFor={props.name}>{props.label}</label>
<input
type={props.type}
name={props.name}
value={props.value}
onChange={props.handleChange}
onBlur={props.handleBlur}
/>
{props.hasError && props.isTouched && <p>{props.errorMessage}</p>}
</div>
))
Note: Formik automatically sets all fields to touched when form is submitted. So, even if nobody touches any field, and tries to submit the form, all error messages will be displayed because Formik will set all fields to touched. This means that the formik.errors.someField && formik.touched.someField
will be true && true
.
Building the form
We are almost done. Last step to make is creating the form wrapper, adding some submit button and all input fields. Two form
element will require two attributes, onSubmit
and noValidate
. We will set the first to Formik's handleSubmit
method. This method will trigger the onSubmit
method we defined in useFormik()
hook.
The noValidate
attribute will disable native form validation. We want to do this because we want the Formik and Yup take care of it. About the content of the form. We could write the code for all input field components. However, we don't have to. We basically need to know only the field name.
Knowing this, we can determine what value should each field render. We can also use the field name to check for errors and render correct error message. We can also easily determine what type the input should be, whether it should be text
, email
or password
. We can create a small and handy object to make this even easier.
Thanks to this, we have to write much less code and make the whole form much shorter. How can we get each field name? We can take the Formik's values
state, iterate over its keys, and render input component for each key, using current field name to use correct data.
<form onSubmit={formik.handleSubmit} noValidate>
{Object.keys(formik.values).map((fieldName) => (
<Input
key={fieldName}
value={formik.values[fieldName]}
errorMessage={formik.errors[fieldName]}
handleBlur={formik.handleBlur}
handleChange={formik.handleChange}
hasError={formik.errors[fieldName]}
isTouched={formik.touched[fieldName]}
label={fieldName[0].toUpperCase() + fieldName.substring(1)}
name={fieldName}
type={inputTypes[fieldName]}
/>
))}
<div>
<button type="submit">Submit</button>
</div>
</form>
Putting the form together
We are almost done. There is one more thing we have to do. We have to put together all the code we created so far and we will have a working form powered by Formik. Well, almost. Our form also needs some logic to handle the onSubmit event and send the data somewhere, but that is up to you.
// Import dependencies:
import { memo } from 'react'
import { useFormik } from 'formik'
import * as Yup from 'yup'
// Import Input component:
import { Input } from './input'
// 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'),
})
// Object with input types:
const inputTypes = {
name: 'text',
email: 'email',
password: 'password',
}
// Create the form component:
export const FormHook = memo(() => {
const formik = useFormik({
initialValues: {
name: '',
email: '',
password: '',
},
onSubmit: (values) => {
console.log(values)
},
validationSchema: formSchema,
})
// Render the form:
return (
<form onSubmit={formik.handleSubmit} noValidate>
{Object.keys(formik.values).map((fieldName) => (
<Input
key={fieldName}
value={formik.values[fieldName]}
errorMessage={formik.errors[fieldName]}
handleBlur={formik.handleBlur}
handleChange={formik.handleChange}
hasError={formik.errors[fieldName]}
isTouched={formik.touched[fieldName]}
label={fieldName[0].toUpperCase() + fieldName.substring(1)}
name={fieldName}
type={inputTypes[fieldName]}
/>
))}
<div>
<button type="submit">Submit</button>
</div>
</form>
)
})
FormHook.displayName = 'FormHook'
Conclusion: 3 Ways to build React forms with Formik pt.3
As you can see, building React forms doesn't have to be lengthy and painful process. With libraries like Formik it can be actually easy. I hope that this tutorial helped you learn how to build React forms using HTML elements and the useFormik()
hook.
Top comments (0)