React Login Form

React Login Form

Create a login form in React using Typescript, Zod and Tailwind

Have you ever felt tired writing checks or creating RegEx for your login form? invalid email, too short password, too long password? πŸ˜‰

well, this post solves that problem for you. Today, we will be building a Login form that uses Typescript (why not?), Zod for validation and creating types, with a sprinkle of Tailwind πŸ’• for styling.

Installing dependencies

Install React

yarn create react-app react-login-form --template typescript

Setup Tailwind. Their documentation is great, you won't get lost ☺️ Tailwind create-react-app documentation

Install other dependencies

yarn add zod react-toastify

Cleaning Up Your React Files

Make sure your file structure looks like this

react-login-form2.png

NOTE: Make sure you import the index.css file into your index.tsx file

Getting Your Hands Dirty

inside the src folder, create a components folder inside which create a button.tsx file. This file will hold our <Button/> component.

// src > components > button.tsx
// button.tsx

import { ButtonHTMLAttributes, DetailedHTMLProps } from "react";

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}
export default function Button(props: ButtonProps) {
  return (
    <button
      className="bg-blue-700 text-white p-3 w-full rounded-md hover:bg-blue-500 focus:bg-blue-500 outline-blue-700"
      {...props}
    >
      {props.value}
    </button>
  );
}

Inside the src folder create a schema folder and create the file login.schema.ts

// src > schema > login.schema.ts
// login.schema.ts

import { z } from "zod";

export const LoginSchema = z.object({
  email: z
    .string({ required_error: "Email is required" })
    .email({ message: "This is not a valid email" }),
  password: z
    .string({ required_error: "Password is required" })
    .min(6, { message: "Password must be at least 6 characters" }),
});

export type LoginType = z.infer<typeof LoginSchema>;

LoginSchema is a zod object z.object({}) with the properties email and password with type of zod string z.string(). this string accepts an object argument with the required_error field which holds a custom error message if a value isn't provided.

email and password have validators have validators chained to their type. These validators perform special checks on the data to be parsed;

.email() checks if the email string is a valid email .min() makes sure the password string is at least 6 characters

these validators accept an object as an argument which contains a message field that contains a custom error to be reported if the validator fails

react-login-form3.png

z.infer is used to get the type definition of any zod object. Here, LoginType is the type definition of LoginSchema

Building The Login Form

import the following files into the App.tsx

// App.tsx

import { ChangeEvent, FormEvent, useState } from "react";
import Button from "./components/button";
import { LoginSchema, LoginType } from "./schema/login.schema";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

create state variables to hook our input fields

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  // update password
  function changePassword(e: ChangeEvent<HTMLInputElement>) {
    setPassword(e.target.value);
  }

  // update the email
  function changeEmail(e: ChangeEvent<HTMLInputElement>) {
    setEmail(e.target.value);
  }

Lets apply a little markup to our application

  const BaseInputStyle =
    "bg-transparent rounded-md border-2 border-blue-300 w-full";
  const BaseLabelStyle = "block w-full font-medium text-blue-900 text-md mb-1";
  const FormStyle =
    "bg-white text-blue-900 w-[90vw] max-w-md h-auto flex items-center flex-col rounded-md shadow-sm py-20 px-14";

  return (
    <>
      <div className="h-screen w-screen bg-gray-200 flex items-center justify-center">
        <form className={FormStyle} >
             <h1 className="text-3xl font-medium p-6 pt-0">Login</h1>
        </form>
      </div>
      <ToastContainer
        position="bottom-right"
        autoClose={2000}
        hideProgressBar
      />
    </>
  );

if you noticed, I moved the reusable styles into variables before the return statement. This helps keep my code neat and easy to debug.

lets start our development server to see how our code is faring

yarn run start

your should see this on your screen

react-login-form5.png

We add the input fields and login button to our form now


          {/* Email input */}
          <label htmlFor="email" className={`${BaseLabelStyle} mt-4`}>
            Email
          </label>
          <input
            id="email"
            type="email"
            placeholder="johndoe@email.com"
            className={BaseInputStyle}
            value={email}
            onChange={changeEmail}
          />

          {/* Password input */}
          <label htmlFor="password" className={`${BaseLabelStyle} mt-5`}>
            Password
          </label>
          <input
            id="password"
            type="password"
            placeholder="******"
            className={`${BaseInputStyle} mb-10`}
            value={password}
            onChange={changePassword}
          />

          <Button value="Login" type="submit" />

with our form in place, we need to add the function for submitting the form and login

Login function

  // login
  // LoginType = {
  // email:string;
  // password:string;
  // }
  function Login(data: LoginType) {
    const res = LoginSchema.safeParse(data);
    if (res.success) toast.success("Login sucessful");
    else {
      // Report all errors at once
      res.error.issues.forEach((issue) => {
        toast.error(issue.message);
      });
    }
  }

we use the safeParse function to validate our login data with the Zod schema we defined earlier LoginSchema. if it fails, we loop through each error and report it to the user using toast.error

we add the function for submitting the form. This function disables page refresh on form submition and calls the login function

  // submit the form
  function submitHandler(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    Login({
      email,
      password,
    });
  }

Lastly, we call the submitHandler function in our form

<form className={FormStyle} onSubmit={submitHandler}>

Well, thats it. ☺️ Thank you for staying this far. if you have any problems, you can checkout the code on Github

Follow me on social media

Github Linked ln Twitter

Β