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