How to build a simple form with validation using yup and formik (beginner friendly)

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

Email

Password

Confirm Password

**Button disabled till all validations are met

This Post would be divided into 3 parts

  1. Building the form

  2. Building the validation

  3. 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

Alt Text

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:

  1. Name: Name must not be less that 3 characters

  2. Email: must be a valid email address

  3. Age: Must be at least 18 years and at most 60 years

  4. Password: Must contain at least one uppercase character, one number, special character and not shorter than 8 characters

  5. Confirm Password: Must match password field

To get started, we would need to install and import 2 libraries into our app.

  1. Yup: Yup is a JavaScript schema builder for value parsing and validation. https://www.npmjs.com/package/yup

  2. 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.

  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:

  1. Install "react-router-dom"

  2. 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.