Just imagine the frustration that may come from filling a poorly validated form :( You'll probably bounce off the page. On surface level, forms are very simple to build, but when it comes to validating them, it may become a bit of a challenge.
I tried to build a form with validations a few weeks back and I struggled a bit with it. When I eventually found my way, I realised it'll be great to write a post about it because a lot more people may be facing this same challenge.
Today, I'll be working you through how I built a form with these fields:
Name
Age
Password
Confirm Password
**Button disabled till all validations are met
This Post would be divided into 3 parts
- Building the form
- Building the validation
- Clicking the submit button should take users to a welcome page
Part One
Let's start off with creating the form in React
Creating the form without breaking the form fields into components
import React from "react";
function Form() {
return (
<form>
<div>
<label htmlFor="name">Name</label>
<input type="text" name="name" id="name" placeholder="Please Enter your name" />
</div>
<div>
<label htmlFor="age">Age</label>
<input type="number" name="age" id="age" placeholder="Please Enter your age" />
</div>
<div>
<label htmlFor="email">Email</label>
<input type="email" name="age" id="email" placeholder="Please Enter your email" />
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
id="password"
placeholder="Please Enter your password"
/>
</div>
<div>
<label htmlFor="confirm-password">Confirm Password</label>
<input
type="password"
name="confirm-password"
id="confirm-password"
placeholder="Please Confirm your password"
/>
</div>
<button>Submit</button>
</form>
);
}
export default Form;
It'll look this way
To reduce code repetition, let us create a form field component that takes: labelName, name, type and placeholder as Props.
The Form Field Component would look like this:
import React from "react";
function FormField({ name, label, ...rest }) {
return (
<div >
<label htmlFor={name}>{label}</label>
<input id={name} name={name} {...rest} />
</div>
);
}
export default FormField;
Refactoring our Form Component would give:
import React from "react";
import FormField from "./FormField";
function Form() {
return (
<form>
<FormField
label="Name"
type="text"
name="name"
placeholder="Please Enter your name"
/>
<FormField
label="Age"
type="number"
name="age"
placeholder="Please Enter your age"
/>
<FormField
label="Email"
type="email"
name="email"
placeholder="Please Enter your email"
/>
<FormField
label="Password"
type="password"
name="password"
placeholder="Please Enter your password"
/>
<FormField
label="Confirm Password"
type="password"
name="confirm-password"
placeholder="Please Confirm your password"
/>
<button type="submit">Submit</button>
</form>
);
}
export default Form;
Part Two
Validations for this form are below:
- Name: Name must not be less that 3 characters
- Email: must be a valid email address
- Age: Must be at least 18 years and at most 60 years
- Password: Must contain at least one uppercase character, one number, special character and not shorter than 8 characters
- Confirm Password: Must match password field
To get started, we would need to install and import 2 libraries into our app.
- Yup: Yup is a JavaScript schema builder for value parsing and validation. https://www.npmjs.com/package/yup
- Formik: Formik is a library that helps you manage state in forms, handle validation, error messages and form submisiion. https://jaredpalmer.com/formik/docs/overview
Next, we would create initial values for our form fields
const initialValues = {
name: "",
age: "",
email: "",
password: "",
confirmPassword: ""
};
After this, we then create our validation schema object using yup
const validationSchema = yup.object().shape({
name: yup
.string()
.required("Name is a required field")
.min(3, "Name must be at least 3 characters"),
age: yup
.number()
.required("Please supply your age")
.min(18, "You must be at least 18 years")
.max(60, "You must be at most 60 years"),
email: yup
.string()
.email()
.required("Email is a required field"),
password: yup
.string()
.required("Please enter your password")
.matches(
/^.*(?=.{8,})((?=.*[!@#$%^&*()\-_=+{};:,<.>]){1})(?=.*\d)((?=.*[a-z]){1})((?=.*[A-Z]){1}).*$/,
"Password must contain at least 8 characters, one uppercase, one number and one special case character"
),
confirmPassword: yup
.string()
.required("Please confirm your password")
.when("password", {
is: password => (password && password.length > 0 ? true : false),
then: yup.string().oneOf([yup.ref("password")], "Password doesn't match")
})
});
Bringing it all together into the Form Component gives
import React from "react";
import { useFormik } from "formik";
import * as yup from "yup";
import FormField from "./FormField";
//setting the initial values
const initialValues = {
name: "",
age: "",
email: "",
password: "",
confirmPassword: ""
};
//creating the validation schema
const validationSchema = yup.object().shape({
name: yup
.string()
.required("A name is required")
.min(2, "Name must be at least 2 characters"),
age: yup
.number()
.required("Please supply your age")
.min(18, "You must be at least 18 years")
.max(60, "You must be at most 60 years"),
email: yup
.string()
.email()
.required("Email is a required field"),
password: yup
.string()
.required("Please enter your password")
.matches(
/^.*(?=.{8,})((?=.*[!@#$%^&*()\-_=+{};:,<.>]){1})(?=.*\d)((?=.*[a-z]){1})((?=.*[A-Z]){1}).*$/,
"Password must contain at least 8 characters, one uppercase, one number and one special case character"
),
confirmPassword: yup
.string()
.required("Please confirm your password")
.when("password", {
is: password => (password && password.length > 0 ? true : false),
then: yup.string().oneOf([yup.ref("password")], "Password doesn't match")
})
});
function Form({ onSubmit }) {
//using useFormik
const formik = useFormik({
initialValues,
validationSchema,
onSubmit
});
return (
<form onSubmit={formik.handleSubmit}>
<FormField
label="Name"
type="text"
name="name"
placeholder="Please Enter your name"
/>
<FormField
label="Age"
type="number"
name="age"
placeholder="Please Enter your age"
/>
<FormField
label="Email"
type="email"
name="email"
placeholder="Please Enter your email"
/>
<FormField
label="Password"
type="password"
name="password"
placeholder="Please Enter your password"
/>
<FormField
label="Confirm Password"
type="password"
name="confirm-password"
placeholder="Please Confirm your password"
/>
<button type="submit">Submit</button>
</form>
);
}
export default Form;
** Note that we passed the onSubmit
callback into the useFormik hook and also passed onSubmit={formik.handleSubmit}
into form.
At this point, our task is almost completed, we only need to utilise a few more props and ensure that the error messages show up
We're going to be making use of getFieldProps.
- getFieldProps is a way to reduce boilerplate (repetitive) code.
- It returns helper methods like
onChange
,onBlur
,value
,name
. * - @see Formik https://jaredpalmer.com/formik/docs/tutorial#getfieldprops
const nameProps = formik.getFieldProps("name");
const ageProps = formik.getFieldProps("age");
const emailProps = formik.getFieldProps("email");
const passwordProps = formik.getFieldProps('password');
const confirmPasswordProps = formik.getFieldProps('confirmPassword');
Lastly, we would need to show error messages when the validation isn't met. For example, for the name field, using formik would be
{formik.touched.name && formik.errors.name ? (
<div>{formik.errors.name}</div>
) : null}
The final code for this form would be
import React from "react";
import { useFormik } from "formik";
import * as yup from "yup";
import FormField from "./FormField";
//setting the initial values
const initialValues = {
name: "",
age: "",
email: "",
password: "",
confirmPassword: ""
};
//creating the validation schema
const validationSchema = yup.object().shape({
name: yup
.string()
.required("A name is required")
.min(2, "Name must be at least 2 characters"),
age: yup
.number()
.required("Please supply your age")
.min(18, "You must be at least 18 years")
.max(60, "You must be at most 60 years"),
email: yup
.string()
.email()
.required("Email is a required field"),
password: yup
.string()
.required("Please enter your password")
.matches(
/^.*(?=.{8,})((?=.*[!@#$%^&*()\-_=+{};:,<.>]){1})(?=.*\d)((?=.*[a-z]){1})((?=.*[A-Z]){1}).*$/,
"Password must contain at least 8 characters, one uppercase, one number and one special case character"
),
confirmPassword: yup
.string()
.required("Please confirm your password")
.when("password", {
is: password => (password && password.length > 0 ? true : false),
then: yup.string().oneOf([yup.ref("password")], "Password doesn't match")
})
});
function Form({ onSubmit }) {
//using useFormik
const formik = useFormik({
initialValues,
validationSchema,
onSubmit
});
//use formik.getFieldProps for input fields
const nameProps = formik.getFieldProps("name");
const ageProps = formik.getFieldProps("age");
const emailProps = formik.getFieldProps("email");
const passwordProps = formik.getFieldProps("password");
const confirmPasswordProps = formik.getFieldProps("confirmPassword");
/**
* getFieldProps is a way to reduce boilerplate (repetitive) code.
* It returns helper methods like `onChange`, `onBlur`, `value`, `name`.
*
* @see Formik https://jaredpalmer.com/formik/docs/tutorial#getfieldprops
*/
return (
<form onSubmit={formik.handleSubmit}>
<FormField
label="Name"
type="text"
placeholder="Please Enter your name"
{...nameProps}
/>
{formik.touched.name && formik.errors.name ? (
<div>{formik.errors.name}</div>
) : null}
<FormField
label="Age"
type="number"
{...ageProps}
placeholder="Please Enter your age"
/>
{formik.touched.age && formik.errors.age ? (
<div>{formik.errors.age}</div>
) : null}
<FormField
label="Email"
type="email"
placeholder="Please Enter your email"
{...emailProps}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<FormField
label="Password"
type="password"
placeholder="Please Enter your password"
{...passwordProps}
/>
{formik.touched.password && formik.errors.password ? (
<div>{formik.errors.password}</div>
) : null}
<FormField
label="Confirm Password"
type="password"
placeholder="Please Confirm your password"
{...confirmPasswordProps}
/>
{formik.touched.confirmPassword && formik.errors.confirmPassword ? (
<div>{formik.errors.confirmPassword}</div>
) : null}
<button type="submit" disabled={!(formik.isValid && formik.dirty)}>Submit</button>
</form>
);
}
export default Form;
Note that to disable the button till all form validations are met, I only passed: disabled={!(formik.isValid && formik.dirty)}
as prop into the button.
Part 3
As with every form, after clicking the submit button, you want users to go to another page. I'll show you how exactly to do that.
(Just in case you'll need further explanation on routing, in my next blog post, I'll take you step by step on how to set up a routing in react).
For now, all you need to do is:
- Install "react-router-dom"
- Create the component or page you'll want users to see after submission of the form. In my case, I'll create a Welcome page
import React from "react";
function Welcome() {
return (
<div>
<h3>Hello and welcome</h3>
</div>
);
}
export default Welcome;
In App put this:
import React from "react";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";
import Form from "./Form";
import Welcome from "./Welcome";
export default function App() {
return (
<Router>
<Switch>
<Route
exact
path="/"
render={props => (
<Form
onSubmit={value => {
props.history.push("/welcome");
}}
/>
)}
/>
<Route exact path="/welcome" component={Welcome} />
</Switch>
</Router>
);
}
Congrats, you just completed this simple tutorial.
I hope this has been really helpful in making you understand how to build a form with validation from scratch using yup and formik.
Kindly leave a comment if you found this useful and check out my other posts.
Top comments (9)
nice blog
Thank you
very helpful for a beginner thank you so much!
This was an awesome article.
It was so helpfull for such a younger developer like me
Amazing! Glad to hear that.
This is a great tutorial! Thank you!
Thank you so much
This is Amazing Esther, thank you for this.
I think we should acknowledge that in the current react version(react version 6), switch has been updated to Routes?
Just incase anyone is stuck on that.