DEV Community 👩‍💻👨‍💻

Cover image for Design your Forms with React, Joi and Tailwind CSS
Ahmed Mohamed
Ahmed Mohamed

Posted on

Design your Forms with React, Joi and Tailwind CSS

Introduction

One of the important things in the Forms Handling Process in Your Frontend App is that be sure that all of the data is true before sending it into the backend but the Form validation process, is a little hard and contains a lot of details that you should repeat it in every project that you need to validate forms data in it.
So to solve this problem we see tens of packages that are used to handle or solve this problem and one of these packages is Joi.
Joi is a package that use to validate forms data that use it in your front-end projects.
And in this article we will design a simple login form with react and tailwind and Joi, so let's get started...👉


1. Initialize project and install Dependencies

First clone the following Repo in your machine by the typing the following command in your terminal:

git clone https://github.com/ahmedmohmd/login-form
Enter fullscreen mode Exit fullscreen mode

Now our project has the following structure:

|
├── public
│   └── index.html
├── README.md
├── src
│   ├── App.js
│   ├── components
│   │   └── LoginForm.jsx
│   ├── index.css
│   ├── index.js
│   └── utils
│       └── formValidate.js
└── tailwind.config.js
├── package.json
├── package-lock.json
├── postcss.config.js
└── .gitignore
Enter fullscreen mode Exit fullscreen mode

Now we will install project Dependencies by typing the following command in Terminal:

npm i
Enter fullscreen mode Exit fullscreen mode

2. Create JSX and Form Styles

Now we can say that we are ready to make our Nice Form, first, we will create JSX and styles of the form.
Go to LoginForm.jsx and type the following code:

LoginForm.jsx:

function LoginForm() {
  return (
    <div className="flex items-center justify-center min-h-screen bg-wi-500 min-w-screen">
      <div className="container flex items-center justify-center p-3 mx-auto">
        <form className="flex flex-col items-center justify-center w-full gap-10 px-5 py-5 shadow-xl rounded-2xl sm:w-1/3">
          <div class="w-full flex flex-col justify-center items-stretch gap-2">
            <label for="email" class="block font-medium text-gray-900 ">
              <span class="bg-purple-100 text-purple-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
                Email
              </span>
            </label>
            <input
              type="email"
              class="placeholder:text-slate-400 placeholder:font-bold outline-none bg-gray-50 border border-gray-300 text-slate-500 font-bold text-md rounded-xl block w-full p-2.5"
              placeholder="john.doe@company.com"
            />
          </div>
          <div class="w-full flex flex-col justify-center items-stretch gap-2">
            <label for="email" class="block font-medium text-gray-900 ">
              <span class="bg-purple-100 text-purple-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
                Password
              </span>
            </label>
            <input
              type="password"
              class="placeholder:text-slate-400 placeholder:font-bold outline-none bg-gray-50 border border-gray-300 text-slate-500 font-bold text-md rounded-xl block w-full p-2.5"
              placeholder="•••••••••"
            />
          </div>
          <button
            type="submit"
            class="text-white bg-blue-500 hover:bg-blue/80 justify-center gap-2 focus:ring-4 focus:outline-none focus:ring-blue-500/50 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:hover:bg-[#FF9119]/80 dark:focus:ring-[#FF9119]/40 mr-2 mb-2"
          >
            <span>Send</span>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              className="w-6 h-6"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
              strokeWidth={2}
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M13 5l7 7-7 7M5 5l7 7-7 7"
              />
            </svg>
          </button>
        </form>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Handle Inputs and Form Submitting

After building JSX and styles of our Component, we will handle input values and events.
LoginForm.jsx:

function LoginForm() {
  const [formData, setFormData] = useState({
    email: "",
    password: "",
  });

  return (
    <div className="flex items-center justify-center min-h-screen bg-wi-500 min-w-screen">
      <form onSubmit={handleSubmit}>
        //* Email Input
        <input
          onChange={(event) => {
            setFormData({ ...formData, email: event.target.value });
          }}
        />

        //* Password Input
        <input
          onChange={(event) => {
            setFormData({ ...formData, password: event.target.value });
          }}
        />
      </form>
    </div>
  );

  function handleSubmit(event) {
    event.preventDefault();
    console.log(JSON.stringify(formData));
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above code, we put an onClick event on every input and listen to any change in inputs values like typing and then assign it in keys (email, password) in the formData State.

After handling changes in inputs values we make an onSubmit event on the form to handle Submit process.
First, we prevent the default behavior of submitting the form by typing event.preventDefault(), and then we log formData as JSON data in console.

4. Validate Form Data by Joi

formValidate.js:

//* Form Validate Function
const formValidate = (formData, schema) => {
  const errors = {};
  const options = { abortEarly: false };
  const { error } = schema.validate(formData, options);

  if (!error) return null;

  if (error)
    for (let item of error.details) {
      errors[item.path[0]] = item.message;
    }

  return errors;
};

export { formValidate };
Enter fullscreen mode Exit fullscreen mode

LoginForm.jsx:

const loginFormSchema = Joi.object({
  email: Joi.string()
    .email({
      tlds: { allow: ["com"] },
    })
    .required(),
  password: Joi.string().min(4).max(8).required(),
});

function LoginForm() {
  const [errors, setErrors] = useState({
    email: "",
    password: "",
  });

  return (
    <div>
      <form>
        //* Email Input
        <input type="password" placeholder="•••••••••" />
        {errors.email ? (
          <div
            class="flex p-4 text-sm text-white bg-red-400 rounded-lg dark:bg-red-200"
            role="alert"
          >
            <svg
              class="inline flex-shrink-0 mr-3 w-5 h-5"
              fill="currentColor"
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                fill-rule="evenodd"
                d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                clip-rule="evenodd"
              ></path>
            </svg>
            <div>{errors.email}</div>
          </div>
        ) : null}
        //* Password Input
        <input type="email" placeholder="john.doe@company.com" />
        {errors.password ? (
          <div
            class="flex p-4 text-sm text-white bg-red-400 rounded-lg dark:bg-red-200"
            role="alert"
          >
            <svg
              class="inline flex-shrink-0 mr-3 w-5 h-5"
              fill="currentColor"
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                fill-rule="evenodd"
                d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                clip-rule="evenodd"
              ></path>
            </svg>
            <div>{errors.password}</div>
          </div>
        ) : null}
      </form>
    </div>
  );

  function handleSubmit(event) {
    event.preventDefault();
    const errorsResult = formValidate(formData, loginFormSchema);

    if (errorsResult) {
      setErrors(errorsResult);
    } else {
      setErrors({});
      console.log(JSON.stringify(formData));
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. First we import the formValidate function in the LoginForm component. this function takes two arguments:
    1. formData
    2. schema

and return error Object that contains our inputs Error if it is found.

  1. Then we will import Joi into our Component and define the Joi schema that the job will compare with our form data.
  2. We will make a state that will contain form data errors object
  3. Then at submitting handler we will check if there are errors or not and update errors to state according to it
  4. finally we will show danger alerts below each input if there are errors.

And this is the final code:

LoginForm.jsx:

import { useState } from "react";
import Joi, { required } from "joi";
import { formValidate } from "../utils/formValidate";

const loginFormSchema = Joi.object({
  email: Joi.string()
    .email({
      tlds: { allow: ["com"] },
    })
    .required(),
  password: Joi.string().min(4).max(8).required(),
});

function LoginForm() {
  const [formData, setFormData] = useState({
    email: "",
    password: "",
  });

  const [errors, setErrors] = useState({
    email: "",
    password: "",
  });

  return (
    <div className="flex items-center justify-center min-h-screen bg-wi-500 min-w-screen">
      <div className="container flex items-center justify-center p-3 mx-auto">
        <form
          onSubmit={handleSubmit}
          className="flex flex-col items-center justify-center w-full gap-10 px-5 py-5 shadow-xl rounded-2xl sm:w-1/3"
        >
          <div class="w-full flex flex-col justify-center items-stretch gap-2">
            <label for="email" class="block font-medium text-gray-900 ">
              <span class="bg-purple-100 text-purple-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
                Email
              </span>
            </label>
            <input
              type="email"
              class="placeholder:text-slate-400 placeholder:font-bold outline-none bg-gray-50 border border-gray-300 text-slate-500 font-bold text-md rounded-xl block w-full p-2.5"
              placeholder="john.doe@company.com"
              onChange={(event) => {
                setFormData({ ...formData, email: event.target.value });
              }}
            />
            {errors.email ? (
              <div
                class="flex p-4 text-sm text-white bg-red-400 rounded-lg dark:bg-red-200"
                role="alert"
              >
                <svg
                  class="inline flex-shrink-0 mr-3 w-5 h-5"
                  fill="currentColor"
                  viewBox="0 0 20 20"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path
                    fill-rule="evenodd"
                    d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                    clip-rule="evenodd"
                  ></path>
                </svg>
                <div>{errors.email}</div>
              </div>
            ) : null}
          </div>
          <div class="w-full flex flex-col justify-center items-stretch gap-2">
            <label for="email" class="block font-medium text-gray-900 ">
              <span class="bg-purple-100 text-purple-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
                Password
              </span>
            </label>
            <input
              type="password"
              class="placeholder:text-slate-400 placeholder:font-bold outline-none bg-gray-50 border border-gray-300 text-slate-500 font-bold text-md rounded-xl block w-full p-2.5"
              placeholder="•••••••••"
              onChange={(event) => {
                setFormData({ ...formData, password: event.target.value });
              }}
            />
            {errors.password ? (
              <div
                class="flex p-4 text-sm text-white bg-red-400 rounded-lg dark:bg-red-200"
                role="alert"
              >
                <svg
                  class="inline flex-shrink-0 mr-3 w-5 h-5"
                  fill="currentColor"
                  viewBox="0 0 20 20"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <path
                    fill-rule="evenodd"
                    d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                    clip-rule="evenodd"
                  ></path>
                </svg>
                <div>{errors.password}</div>
              </div>
            ) : null}
          </div>
          <button
            type="submit"
            class="text-white bg-blue-500 hover:bg-blue/80 justify-center gap-2 focus:ring-4 focus:outline-none focus:ring-blue-500/50 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center dark:hover:bg-[#FF9119]/80 dark:focus:ring-[#FF9119]/40 mr-2 mb-2"
          >
            <span>Send</span>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              className="w-6 h-6"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
              strokeWidth={2}
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M13 5l7 7-7 7M5 5l7 7-7 7"
              />
            </svg>
          </button>
        </form>
      </div>
    </div>
  );

  function handleSubmit(event) {
    event.preventDefault();
    const errorsResult = formValidate(formData, loginFormSchema);

    if (errorsResult) {
      setErrors(errorsResult);
    } else {
      setErrors({});
      console.log(JSON.stringify(formData));
    }
  }
}

export default LoginForm;
Enter fullscreen mode Exit fullscreen mode

And finally result will be like this:
Login Form


Conclusion

After reading this article you should take a look at the Joi package and how it can be easier from the validation process, there are a lot of other packages like Joi you can use, but the principle is the same.
I hope that this article helps you, thanks for reading, and see you in the next article.

Top comments (0)

🌚 Life is too short to browse without dark mode