As developers, it is our job to ensure that when users interact with the forms we set up, the data they send across is in the form we expect.
In this article, we will learn how to handle form validation and track the state of forms without the aid of a form library.
Next, we will see how the Formik library works. We’ll learn how it can be used incrementally with HTML input fields and custom validation rules. Then we will set up form validation using Yup and Formik's custom components and understand how Yup works well with Formik in handling Form validation. We will implement these form validation methods to validate a simple sign up form I have set up.
Note: This article requires a basic understanding of React.
Form Validation In React
On its own, React is powerful enough for us to be able to set up custom validation for our forms. Let’s see how to do that. We’ll start by creating our form component with initial state values. The following sandbox holds the code for our form:
Form validation without the use of a library
const Form = () => {
const intialValues = { email: "", password: "" };
const [formValues, setFormValues] = useState(intialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
}
With the useState hook, we set state variables for the formValues, formErrors and isSubmitting.
The formValues variable holds the data the user puts into the input fields.
The formErrors variable holds the errors for each input field.
The isSubmitting variable is a boolean that tracks if the form is being submitted or not. This will be true only when there are no errors in the form.
const submitForm = () => {
console.log(formValues);
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
setFormErrors(validate(formValues));
setIsSubmitting(true);
};
const validate = (values) => {
let errors = {};
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
if (!values.email) {
errors.email = "Cannot be blank";
} else if (!regex.test(values.email)) {
errors.email = "Invalid email format";
}
if (!values.password) {
errors.password = "Cannot be blank";
} else if (values.password.length < 4) {
errors.password = "Password must be more than 4 characters";
}
return errors;
};
useEffect(() => {
if (Object.keys(formErrors).length === 0 && isSubmitting) {
submitForm();
}
}, [formErrors]);
Here, we have 4 form handlers and a useEffect set up to handle the functionality of our form.
handleChange
This keeps the inputs in sync with the formValues state and updates the state as the user types.
validate
We pass in the formValues object as a argument to this function, then based on the email and password meeting the validation tests, the errors object is populated and returned.
handleSubmit
Whenever the form is submitted, the formErrors state variable is populated with whatever errors may exist using the setFormErrors(validate(formValues)) method.
useEffect
Here, we check if the formErrors object is empty, and if isSubmitting is true. If this check holds true, then the submitForm() helper is called. It has single dependency, which is the formErrors object. This means it only runs when the formErrors object changes.
submitForm: this handles the submission of the form data.
return (
Sign in to continue
{Object.keys(formErrors).length === 0 && isSubmitting && (
Signed in successfully
)}
);
Here, we pass in the handleChange helper functions to the inputs’ onChange attribute. We link the value of the inputs to the formValues object, making them controlled inputs. From the React docs, controlled inputs are inputs whose values are controlled by React. An input-error style is applied if there are any errors related to that specific input field. An error message is conditionally displayed beneath each input if there are any errors related to that specific input field. Finally, we check if there are any errors in the errors object and if isSubmitting is true. If these conditions hold true, then we display a message notifying the user that they signed in successfully.
With this, we have a fully functional and validated form set up without the aid of a library. However, a form library like Formik with the aid of Yup can simplify the complexities of handling forms for us.
What Are Formik And Yup?
Right from the docs:
“Formik is a small library that helps you with the 3 most annoying parts in handling forms:
Getting values in and out of form state.
Validation and error messages
Handling form submission.
Formik is a flexible library. It allows you to decide when and how much you want to use it. We can control how much functionality of the Formik library we use. It can be used with HTML input fields and custom validation rules, or Yup and the custom components it provides. Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React.
Yup is a JavaScript object schema validator. While it has many powerful features, we’ll focus on how it helps us create custom validation rules so we don’t have to. This is a sample Yup object schema for a sign-up form. We’ll go into Yup and how it works in depth later in the article.
const SignUpSchema = Yup.object().shape({
firstName: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Firstname is required"),
lastName: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Lastname is required"),
phoneNumber: Yup.string()
.required("Phone number is required")
.matches(
/^([0]{1}|\+?[234]{3})([7-9]{1})([0|1]{1})([\d]{1})([\d]{7})$/g,
"Invalid phone number"
),
email: Yup.string().email().required("Email is required"),
password: Yup.string()
.required("Password is required")
.min(6, "Password is too short - should be 6 chars minimum"),
});
Formik, HTML Input Fields And Custom Validation Rules
The following sandbox holds the code for this form set up:
The first thing we have to do is install Formik.
npm i formik
Then we can go ahead to import it in the file where we’ll make use of it.
import { Formik } from "formik";
Before creating the component, we need to create an initialValues and validate object which we’ll pass as props to the Formik component when we set it up. initialValues and validate are code snippets, not normal words.
The decision to do this outside the component is not a technical one, but rather for readability of our code.
const initialValues = {
email: "",
password: ""
};
initialValues: is an object that describes the initial values of the respective form fields. The name given to each key in the initialValues must correspond with the value of the name of the input field we want Formik to watch.
const validate = (values) => {
let errors = {};
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
if (!values.email) {
errors.email = "Email is required";
} else if (!regex.test(values.email)) {
errors.email = "Invalid Email";
}
if (!values.password) {
errors.password = "Password is required";
} else if (values.password.length < 4) {
errors.password = "Password too short";
}
return errors;
};
validate: this accepts a function that handles the form validation. The function accepts an object in the form of data values as an argument and validates each property in the object based on the rules defined. Each key in the values object must correspond with the name of the input field.
const submitForm = (values) => {
console.log(values);
};
onSubmit: This handles what happens after the user submits. The onSubmit prop takes a callback function that will only run when there are no errors, meaning the user inputs are valid.
const SignInForm = () => {
return (
validate={validate}
onSubmit={submitForm}
>
{(formik) => {
const {
values,
handleChange,
handleSubmit,
errors,
touched,
handleBlur,
isValid,
dirty
} = formik;
return (
Sign in to continue
);
}}
);
};
We pass in the initialValues object, and the submitForm and validate functions we defined earlier into Formik’s initialValues, onSubmit and validate props respectively.
Using the render props pattern, we have access to even more props the Formik API provides.
values
This holds the values of the user inputs.
handleChange
This is the input change event handler. It is passed to the input field . It handles the changes of the user inputs.
handleSubmit
The form submission handler. It is passed into the form
Field
In the background, this automatically links the form input’s onChange, onBlur and value attributes to Formik’s handleChange, handleBlur, and values object respectively. It uses the name prop to match up with the state and automatically keeps the state in sync with the input value. With this component, we can decide to display it as an input field we want using it’s as property. For example, will render a textarea. By default, it renders an HTML input field.
ErrorMessage
It handles rendering the error message for its respective field based on the value given to the name prop, which corresponds to the
We pass the signInSchema into Formik using the validationSchema prop. The Formik team loves the Yup validation library so they created a specific prop for Yup called validationSchema which transforms errors into objects and matches against their values and touched functions.
Conclusion
Users do not know or care how you handle form validation. However, for you the developer, it should be as painless a process as possible, and I believe Formik stands out as a solid choice in that regard.
We have successfully looked at some of the options available to us when validating forms in React. We have seen how Formik can be used incrementally, and how it pairs well with Yup in handling form validation.
Resources
Formik Docs
Yup Docs
Validation with Yup