Formik is a library that makes creating complex forms a snap. Testing Library (previously known as React Testing Library) is the gold standard when it comes to testing React applications. While working with Formik for the last couple of years, I have found that some developers are not comfortable with testing it.
For this exercise, we will make a simple React application using create-react-app that renders a login form, has some simple validation and makes an API call to verify the username and password. If the API tells us that the username and password are correct, our application should redirect the user to another page. If incorrect, we should display a message to the user to that effect.
We can start by bootstrapping a new React application with create-react-app. I decided to use the TypeScript template.
npx create-react-app my-app --template typescript
**** NOTE ****
Before getting started, make sure you update all of your dependencies to their latest versions. At the time of writing this article, there was a problem with using Create React App with testing library. To resolve this problem, I had to install jest-environment-jsdom-sixteen
and update my test
script.
"test": "react-scripts test --env=jest-environment-jsdom-sixteen"
Next, I created a file called LoginForm
, and added the following.
import React from "react";
import cx from "classnames";
import { Formik } from "formik";
export const LoginForm = () => {
return (
<Formik
initialValues={{ email: "", password: "" }}
validate={values => {
const errors: Record<string, string> = {};
if (!values.email) {
errors.email = "Required";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Invalid email address";
}
if (!values.password) {
errors.password = "Required";
}
return errors;
}}
onSubmit={async (values, { setStatus, setSubmitting }) => {
setSubmitting(true);
try {
const response = await fetch("/api/sign-in", {
method: "POST", // or 'PUT'
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(values)
});
if (!response.ok) {
throw Error(response.statusText);
}
window.location.replace("/dashboard");
} catch (e) {
console.log("e",
e);
const apiError = {
apiError: "Invalid username or password"
};
setStatus(apiError);
} finally {
setSubmitting(false);
}
}}
>
{({
values,
status,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting
/* and other goodies */
}) => {
const hasError = Object.keys(errors).length > 0;
return (
<div className="w-full max-w-xs">
{status && status["apiError"] && (
<div
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4"
role="alert"
>
{status["apiError"]}
</div>
)}
<form
className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
onSubmit={handleSubmit}
>
<div className="mb-4">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="email"
>
Email
</label>
<input
className={cx(
"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline",
{
"border-red-500": touched["email"] && errors["email"]
}
)}
id="email"
name="email"
type="text"
placeholder="Email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
{touched["email"] && errors["email"] && (
<p className="pt-2 text-red-500 text-xs italic">
{errors["email"]}
</p>
)}
</div>
<div className="mb-6">
<label
className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="password"
>
Password
</label>
<input
className={cx(
"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline",
{
"border-red-500": touched["email"] && errors["email"]
}
)}
id="password"
name="password"
type="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
placeholder="******************"
/>
{touched["password"] && errors["password"] && (
<p className="text-red-500 text-xs italic">
{errors["password"]}
</p>
)}
</div>
<div className="flex items-center justify-between">
<button
disabled={isSubmitting || hasError}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit"
>
Sign In
</button>
<a
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
href="#"
>
Forgot Password?
</a>
</div>
</form>
<p className="text-center text-gray-500 text-xs">
©2020 Acme Corp. All rights reserved.
</p>
</div>
);
}}
</Formik>
);
};
There’s a lot going on in here, so let’s just concentrate on a few things.
touched
.submit
function, and use setStatus
from Formik if we get an error, otherwise we use window.location.replace
to redirect the user.After playing around with our form in the browser, we can start by writing our first tests, but first, we need to decide what to test.
#react #javascript #programming #testing #developer