Yup Validation and Formik Form Control in React/VITE
Introduction
In the vast realm of web development, handling form data is a common yet crucial task. With the advent of powerful tools like React and VITE, developers have access to efficient libraries such as Yup and Formik to streamline the form validation process. In this blog post, we'll explore the integration of Yup for validation and Formik for form control in a React/VITE environment.
Main Focus
As previously mentioned, we will go over what exactly makes Yup and Formik a powerhouse in validation and form control. We will explore some standard syntax and choices that come with utilizing both Yup and Formik together. Additionally, we will review some code examples that will break down how to debug/trace when some of your Yup / Formik logic is: !Yup && !Formik.
It is important to note that there are different versions of each of these libraries and it is important to review the library documentation to decide which version best fits your needs. Before we get more into each library, install the latest version of both Yup and Formik (also available with yarn install):
npm i yup
npm i formik
import { Formik, Field, Form, ErrorMessage } from 'formik' import * as Yup from 'yup'
Form Validation with Yup
Yup provides a simple and efficient way to validate form data. It accomplishes this task by leveraging declarative validation methods that are used alongside form fields. Some of the common methods are:
.required()
: Ensures that the field is not empty..min()
and.max()
: Specifies minimum and maximum length or value constraints..email()
: Validates that the input is a valid email address..url()
: Verifies that the input is a valid URL..matches()
: Allows you to define a regular expression pattern for validation.
Custom Validations with.test()
One of the standout features of Yup is the ability to create custom validation rules using the .test()
method. This method allows you to define your validation logic based on the field's value and the current form state. Here's a brief example of using .test()
to ensure a password meets specific criteria:
const passwordSchema = yup.string()
.required('Password is required')
.min(8, 'Password must be at least 8 characters')
.test('strong-password', 'Password must contain at least one uppercase letter, one lowercase letter, and one number', value => {
return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value);
});
In the code snippet below you can see the validationSchema
established using Yup. This schema ensures that various fields like artist
, albumCover
, and others adhere to specific validation rules. For instance, the artist
field must be a non-empty string, and similar rules apply to other fields.
//Validation using Yup
//Create a validationSchema for each form input and customize the error message
const validationSchema = Yup.object().shape({
inCollection: Yup.boolean().required('Collection status is required'),
artist: Yup.string().required('Artist is required'),
albumCover: Yup.string().required('Album cover image link is required'),
title: Yup.string().required('Title is required'),
released: Yup.string().required('Release year is required'),
label: Yup.string().required('Label is required'),
})
Form Control with Formik
Formik simplifies handling form state, form submission, and validation. It accomplishes this through built-in utilities and in the coming code examples we will utilize a few of these utilities. One thing to mention is you will notice the lack of useState. This is because one benefit of Formik is that it automatically manages form state, eliminating the need for developers to manually handle state changes and updates. Additionally, it also supports asynchronous form submission.
In the examples provided, we are using Formik Component Integration within our AlbumsForm
component. This integration allows for seamless integration into React applications as it allows developers to wrap their components with the Formik high-order component.
Breaking Down The Code:
We initialize Formik with an initialValues
object, defined validationSchema
by Yup, and an onSubmit
function to handle form submissions. The combination of Yup and Formik is what makes these libraries very resourceful. There are a few syntax variations but below I have highlighted what is the preferred method based on the library documentation.
const initialValues = { //Establish your forms initial values
inCollection: true,
artist: '',
albumCover: '',
title: '',
released: '',
label: '',
}
// I had to comment out the below return for text clarity
// Remove the below comment (//) and keep the return (
//return (
<div>
{formStatus && <div style={{ color: 'green' }}>{formStatus}</div>}
<Formik
initialValues={initialValues}
validationSchema={validationSchema} //validates using validationSchema
onSubmit={async (values, { //pass desired formik functions to assist with form control
setSubmitting, resetForm, setStatus }) => {
try {
await handleAddAlbum(values) // Callback to handle POST
setFormStatus('Form submitted successfully!') // Message appears on successful POST
setTimeout(() => {
navigate('/') // Navigate back to the main library after ...
setFormStatus('') // reset the displayed Formstatus back
}, 2000) // 2000 milliseconds / 2 seconds
resetForm()
} catch (validationError) { //upon Submit > forEach field
const errors = {} // not completed display a error at the top of the form
validationError.inner.forEach((e) => {
errors[e.path] = e.message
})
setStatus({}) //removes prior status if one was set
setSubmitting(false) //setSubmitting handles form control
}
}}
>
In the above code example, we used quite a few different Formik-specific utilities such as functions that directly assist with form control.
setSubmitting
is particularly useful when dealing with asynchronous form submissions. After initiating an asynchronous operation (e.g., submitting data to a server), you can callsetSubmitting(true)
to indicate that the form is in the process of being submitted. Once the operation is complete, you can usesetSubmitting(false)
to mark the end of the submission process.After successfully submitting a form or when a "reset" action is triggered, you can call
resetForm()
to reset the form.You can use
setStatus
to update a status message or store additional data related to the form. For example, you might use it to display a success message or handle custom scenarios specific to your application's requirements.
validationError
is a property and not a function as when a validation error occurs during form submission, Formik sets the validationError
property to the validation error message. This can be useful for displaying a general error message related to the entire form, not tied to a specific field.
The form renders input fields for attributes like artist name, album cover link, and more. Any validation errors are displayed user-friendly below the corresponding input fields or another spot if you desire. Within the example code below, I highlight the two options I considered.
Display Errors / Successful Form submission at the top of the form:
//.. return ( and prior code above
<div>
{formStatus && <div style={{ color: 'green' }}>{formStatus}</div>}
<Formik
initialValues={initialValues}
validationSchema={validationSchema} //validates using validationSchema
//.. addtional JSX here
{({ isSubmitting, submitCount, errors }) => (
<Form>
{/* Displaying each error mapped at the top of the form */}
{submitCount > 0 &&
Object.keys(errors).map((field) => (
<div key={field} style={errorStyle}>
{errors[field]}
</div>
))}
Display Errors beneath each form option:
//.. prior JSX here
>
{({ isSubmitting }) => (
<Form>
{fieldInfo.map((field) => (
<div key={field.name}>
<label htmlFor={field.name}>{field.label}:</label>
{field.type === 'checkbox' ? (
<Field type={field.type} id={field.name} name={field.name} />
) : (
<Field
type={field.type}
id={field.name}
name={field.name}
placeholder={field.placeholder}
/>
)}
// Remove these two lines for code to work
// Below the inputs is where the ErrorMessages will render
<ErrorMessage name={field.name} component="div" style={errorStyle} />
</div>
))}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
</div>
)
}
//.. addtional JSX here , repeat similar syntax for each form input
In the above code, we utilized the Formik components named Field
and ErrorMessage
.
The
Field
component simplifies the binding of form elements to the underlying form state. This is exactly what we previously mentioned when we said Formik would handle the form state for us and allow us to easily manage specific field values.The
ErrorMessage
displays error messages that are associated with a specific form field that is leveraged through field-level validation. Thename
prop of<ErrorMessage>
should match thename
prop of the corresponding<Field>
component. The error message defined in thevalidate
function will be displayed within the<div>
element and thecomponent
prop specifies the type of HTML element or custom component that should be used to render the error message.
Tip: You can even customize these errors with a variable, CSS, or even a CSS library.
const errorStyle = { color: 'red' , fontWeight: 'bold'}
Final note: When the user leaves the input field, the onBlur event handler will be called. This will update the touched object in the Formik state, which will indicate that the field has been visited. Formik Handles all of this for you! Be careful if you choose to utilize the useFormik (hook) instead, as it has less functionality.
Debugging Your Validation/Form Control
Nothing is more difficult than learning the intricacies of a new library and fumbling through Syntax and errors.
Debugging in general can be a full topic of its own with many references and developer tool examples. With that said, I wanted to slightly touch on it by getting you started with something as an example.
I am showing mostly console.log() debugging below and with only one debugger. I would suggest getting familiar with a debugger as it can be much more useful in following the code stack.
<div>
{formStatus && <div style={{ color: 'green' }}>{formStatus}</div>}
<Formik
initialValues={initialValues}
validationSchema={validationSchema} //validates using validationSchema
onSubmit={async (values, { //pass desired formik functions to assist with form control
setSubmitting, resetForm, setStatus }) => {
try {
debugger
console.log("Before handleAddAlbum")
await handleAddAlbum(values) // Callback to handle POST
console.log("After handleAddAlbum")
console.log("After handleAddAlbum")
console.log("Before setStatus:", Formik)
setFormStatus('Form submitted successfully!') // Message appears on successful POST
console.log("after setStatus:", Formik)
setTimeout(() => {
navigate('/') // Navigate back to the main library after ...
setFormStatus('') // reset the displayed Formstatus back
}, 2000) // 2000 milliseconds / 2 seconds
resetForm()
console.log("After resetForm:", Formik)
} catch (validationError) { //upon Submit > forEach field
const errors = {} // not completed display a error at the top of the form
validationError.inner.forEach((e) => {
errors[e.path] = e.message
})
console.log("After setErrors:", Formik)
setStatus({}) //removes prior status if one was set
setSubmitting(false) //setSubmitting handles form control
}
}}
Conclusion
Form validation and control can often be a daunting task for new developers and a tedious one for more senior developers. Through utilizing a form validation and form control library such as the Yup/Formik combination, you will learn to leverage these libraries so you can create a robust and user-friendly form.
Nevertheless, no good library is understood without first going through that library's supportive material and I highly suggest reviewing the related information and reference links before getting started on your own Yup/Formik controlled form.
Related Information:
MDN - Native/Basic Form Controls
Additional Resources Provided Directly by Formik
References:
jquense/yup: Dead simple Object schema validation (Very detailed)