visit
In this tutorial, you’ll learn how to create and validate forms in React, ensuring that users provide data in a specified format.
Additionally, you’ll learn how to use validation schema libraries, such as Yup and Zod, and other form management libraries, like React Hook Form and Formik.
import { useState } from "react";
const App = () => {
//👇🏻 states for modifying the form inputs
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
//👇🏻 executes when a user submits the form
const handleSubmit = (event) => {
event.preventDefault();
console.log({ name, email, message }); setName(""); setEmail(""); setMessage("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<h2>Contact Us</h2>
<label htmlFor='name'>Name</label>
<input
type='text'
id='name'
value={name}
onChange={(e) => setName(e.target.value)}
/>
<label htmlFor='email'>Email</label>
<input
type='email'
id='email'
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<label htmlFor='message'>Message</label>
<textarea
rows={5}
id='message'
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type='submit'>SEND</button>
</form>
</div>
);
};
From the code snippet above, the React states manage and store the form values, enabling us to control the state values using the `setState` function and perform various actions as the user provides the input.
const App = () => {
//👇🏻 executes when a user submits the form
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const object = Object.fromEntries(formData);
console.log(object);
};
return (
<div>
<form onSubmit={handleSubmit}>
<h2>Contact Us</h2>
<label htmlFor='name'>Name</label>
<input type='text' id='name' name='name' />
<label htmlFor='email'>Email</label>
<input type='email' id='email' name='email' />
<label htmlFor='message'>Message</label>
<textarea rows={5} id='message' name='message' />
<button type='submit'>SEND</button>
</form>
</div>
);
};
An uncontrolled form can also be created using the hook.
import { useRef } from "react";
const App = () => {
const nameRef = useRef(null);
const emailRef = useRef(null);
const messageRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
// Access input values directly using refs
const name = nameRef.current.value;
const email = emailRef.current.value;
const message = messageRef.current.value;
console.log({ name, email, message });
};
return (
<div>
<form onSubmit={handleSubmit}>
<h2>Contact Us</h2>
<label htmlFor='name'>Name</label>
<input type='text' id='name' ref={nameRef} />
<label htmlFor='email'>Email</label>
<input type='email' id='email' ref={emailRef} />
<label htmlFor='message'>Message</label>
<textarea rows={5} id='message' ref={messageRef} />
<button type='submit'>SEND</button>
</form>
</div>
);
};
import { useState } from "react";
const App = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [email, setEmail] = useState("");
//👇🏻 executes when a user submits the form
const handleSubmit = (event) => {
event.preventDefault();
console.log({ username, password, email });
alert("Form Submitted ✅");
};
return (
<div>
<form onSubmit={handleSubmit}>
<h2>Log in</h2>
<label htmlFor='name'>Username</label>
<input
type='text'
id='username'
name='username'
value={username}
onChange={(e) => setUsername(e.target.value)}
required
minLength={6}
/>
<label htmlFor='email'>Email</label>
<input
type='email'
id='email'
name='email'
value={email}
required
onChange={(e) => setEmail(e.target.value)}
/>
<label htmlFor='password'>Password</label>
<input
type='password'
id='password'
name='password'
value={password}
required
minLength={8}
onChange={(e) => setPassword(e.target.value)}
/>
<button type='submit'>REGISTER</button>
</form>
</div>
);
};
Consider the code snippet below:
import { useState } from "react";
const App = () => {
//👇🏻 state for each input
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [email, setEmail] = useState("");
//👇🏻 state for error messages
const [errorMessage, setErrorMessage] = useState({
username: "",
email: "",
password: "",
});
// 👇🏻 Function to update error messages
const updateErrorState = (fieldName, message) => {
setErrorMessage((prevState) => ({
...prevState,
[fieldName]: message,
}));
};
//👇🏻 function for validating inputs
const validateInput = (data) => {
const { value, name } = data;
if (name === "username") {
if (value.trim() === "") {
updateErrorState(name, "Username is required");
} else if (value.length < 6) {
updateErrorState(name, "Username must be at least 6 characters");
} else {
updateErrorState(name, "");
}
} else if (name === "email") {
if (value.trim() === "") {
updateErrorState(name, "Email is required");
} else {
updateErrorState(name, "");
}
} else {
if (value.trim() === "") {
updateErrorState(name, "Password is required");
} else if (value.length < 8) {
updateErrorState(name, "Password must be at least 8 characters");
} else {
updateErrorState(name, "");
}
}
};
//👇🏻 submits the form
const handleSubmit = (e) => {
e.preventDefault();
if (
Object.values(errorMessage).some((message) => message !== "") ||
!email ||
!password ||
!username
) {
alert("Invalid Credentials ❌");
return;
}
alert("Form Submitted ✅");
};
return (
<div>
<form onSubmit={handleSubmit}>
<h2>Log in</h2>
<label htmlFor='name'>Username</label>
<input
type='text'
id='username'
name='username'
value={username}
onChange={(e) => {
setUsername(e.target.value);
validateInput(e.target);
}}
/>
{errorMessage.username && <p>{errorMessage.username}</p>}
<label htmlFor='email'>Email</label>
<input
type='email'
id='email'
name='email'
value={email}
onChange={(e) => {
setEmail(e.target.value);
validateInput(e.target);
}}
/>
{errorMessage.email && <p>{errorMessage.email}</p>}
<label htmlFor='password'>Password</label>
<input
type='password'
id='password'
name='password'
value={password}
onChange={(e) => {
setPassword(e.target.value);
validateInput(e.target);
}}
/>
{errorMessage.password && <p>{errorMessage.password}</p>}
<button type='submit'>REGISTER</button>
</form>
</div>
);
};
Yup is a simple JavaScript schema validator that provides multiple functions to enable us to validate whether an object matches a particular set of rules.
npm install yup
For example, the schema for a form containing an email, a username with a minimum of six characters, and a password of at least eight characters is shown below:
import { object, string } from "yup";
//👇🏻 user schema with Yup
const userSchema = object().shape({
username: string()
.required("Username is required")
.min(6, "Username must be at least 6 characters"),
email: string().email("Invalid Email").required("Email is required"),
password: string()
.required("Password is required")
.min(8, "Password must be at least 8 characters"),
});
The userSchema object describes the form inputs.
Let’s examine the complete code for the form:
import { useState } from "react";
import { object, string } from "yup";
const App = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [email, setEmail] = useState("");
const [errorMessage, setErrorMessage] = useState({
username: "",
email: "",
password: "",
});
//👇🏻 user schema with Yup
const userSchema = object().shape({
username: string()
.required("Username is required")
.min(6, "Username must be at least 6 characters"),
email: string().email("Invalid Email").required("Email is required"),
password: string()
.required("Password is required")
.min(8, "Password must be at least 8 characters"),
});
//👇🏻 submits the form
const handleSubmit = async (e) => {
e.preventDefault();
try {
await userSchema.validate(
{ username, email, password },
{ abortEarly: false }
);
console.log({ email, username, password });
setErrorMessage({ username: "", email: "", password: "" });
alert("Form Submitted ✅");
} catch (error) {
//👇🏻 update the errors
const errors = error.inner.reduce((accumulator, currentValue) => {
accumulator[currentValue.path] = currentValue.message;
return accumulator;
}, {});
setErrorMessage(errors);
return;
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<h2>Log in</h2>
<label htmlFor='name'>Username</label>
<input
type='text'
id='username'
name='username'
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
{errorMessage.username && <p>{errorMessage.username}</p>}
<label htmlFor='email'>Email</label>
<input
type='email'
id='email'
name='email'
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{errorMessage.email && <p>{errorMessage.email}</p>}
<label htmlFor='password'>Password</label>
<input
type='password'
id='password'
name='password'
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{errorMessage.password && <p>{errorMessage.password}</p>}
<button type='submit'>REGISTER</button>
</form>
</div>
);
};
npm install zod
import { z, ZodError } from "zod";
//👇🏻 user schema with Zod
const schema = z.object({
username: z.string().min(6, {
message: "Username must be at least 6 characters",
}),
email: z.string().email({ message: "Invalid email address" }),
password: z.string().min(8, {
message: "Password must be at least 8 characters",
}),
});
Let’s examine the complete code for the form:
import { useState } from "react";
import { z, ZodError } from "zod";
const App = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [email, setEmail] = useState("");
const [errorMessage, setErrorMessage] = useState({
username: "",
email: "",
password: "",
});
//👇🏻 user schema with Zod
const schema = z.object({
username: z.string().min(6, {
message: "Username must be at least 6 characters",
}),
email: z.string().email({ message: "Invalid email address" }),
password: z.string().min(8, {
message: "Password must be at least 8 characters",
}),
});
//👇🏻 submits the form
const handleSubmit = (e) => {
e.preventDefault();
try {
//👇🏻 validates the inputs
schema.parse({ username, email, password });
console.log({ email, username, password });
setErrorMessage({ username: "", email: "", password: "" });
alert("Form Submitted ✅");
} catch (error) {
//👇🏻 updates error message
if (error instanceof ZodError) {
const newFormErrors = {};
error.errors.forEach((err) => {
const fieldName = err.path[0];
newFormErrors[fieldName] = err.message;
});
setErrorMessage(newFormErrors);
}
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<h2>Log in</h2>
<label htmlFor='name'>Username</label>
<input
type='text'
id='username'
name='username'
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
{errorMessage.username && <p>{errorMessage.username}</p>}
<label htmlFor='email'>Email</label>
<input
type='email'
id='email'
name='email'
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{errorMessage.email && <p>{errorMessage.email}</p>}
<label htmlFor='password'>Password</label>
<input
type='password'
id='password'
name='password'
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{errorMessage.password && <p>{errorMessage.password}</p>}
<button type='submit'>REGISTER</button>
</form>
</div>
);
};
React Hook Form is a popular library that enables us to build simple, scalable, and highly performant forms. It manages the form inputs, enforces validation, and displays the necessary errors when necessary.
npm install react-hook-form
import { useForm } from "react-hook-form";
const App = () => {
const { register, handleSubmit, formState } = useForm();
const { errors } = formState;
//👇🏻 submits the form
const onSubmit = (data) => {
console.log("Form Submitted", data);
alert("Form Submitted ✅");
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<h2>Log in</h2>
<label htmlFor='name'>Username</label>
<input
type='text'
id='username'
{...register("username", {
required: "Username is required",
minLength: {
value: 6,
message: "Username must be at least 6 characters",
},
})}
/>
<p>{errors.username?.message}</p>
<label htmlFor='email'>Email</label>
<input
type='email'
id='email'
{...register("email", {
required: "Email is required",
pattern: {
value: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
message: "Invalid email address",
},
})}
/>
<p>{errors.email?.message}</p>
<label htmlFor='password'>Password</label>
<input
type='password'
id='password'
{...register("password", {
required: "Password is required",
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
})}
/>
<p>{errors.password?.message}</p>
<button type='submit'>REGISTER</button>
</form>
</div>
);
};
npm install @hookform/resolvers yup
import * as yup from "yup";
const schema = yup
.object()
.shape({
username: yup
.string()
.required("Username is required")
.min(6, "Username must be at least 6 characters"),
email: yup.string().required("Email is required"),
password: yup
.string()
.required("Password is required")
.min(8, "Password must be at least 8 characters"),
})
.required();
import { yupResolver } from "@hookform/resolvers/yup";
const App = () => {
const { register, handleSubmit, formState } = useForm({
resolver: yupResolver(schema),
});
const { errors } = formState;
//👇🏻 submits the form
const onSubmit = (data) => {
console.log("Form Submitted", data);
alert("Form Submitted ✅");
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<h2>Log in</h2>
<label htmlFor='name'>Username</label>
<input type='text' id='username' {...register("username")} />
<p>{errors.username?.message}</p>
<label htmlFor='email'>Email</label>
<input type='email' id='email' {...register("email")} />
<p>{errors.email?.message}</p>
<label htmlFor='password'>Password</label>
<input type='password' id='password' {...register("password")} />
<p className='text-red-500 mb-4 '>{errors.password?.message}</p>
<button type='submit'>REGISTER</button>
</form>
</div>
);
};
npm install @hookform/resolvers zod
import * as z from "zod";
const schema = z.object({
username: z.string().min(6, {
message: "Username must be at least 6 characters",
}),
email: z.string().email({ message: "Invalid email address" }),
password: z.string().min(8, {
message: "Password must be at least 8 characters",
}),
});
npm install formik
The useFormik hook accepts an object containing the initial values of the form fields, the onSubmit function, and the validation function.
import { useFormik } from "formik";
const formik = useFormik({
initialValues: {
//👇🏻 form default input
username: "",
email: "",
password: "",
},
//👇🏻 handles form submission
onSubmit: (values) => {
console.log(values);
},
//👇🏻 handles form validation
validate: (values) => {
//👉🏻 validate form inputs
},
});
import { useFormik } from "formik";
//👇🏻 form validation function
const validate = (values) => {
// validate function
let errors = {};
if (!values.username) {
errors.username = "Required";
} else if (values.username.length < 6) {
errors.username = "Must be 6 characters or more";
}
if (!values.email) {
errors.email = "Required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) {
errors.email = "Invalid email address";
}
if (!values.password) {
errors.password = "Required";
} else if (values.password.length < 8) {
errors.password = "Must be 8 characters or more";
} else if (values.password === "password") {
errors.password = "Must not be password";
}
return errors;
};
//👇🏻 form submission function
const onSubmit = (values) => {
console.log(values);
alert("Form submitted successfully ✅");
};
const App = () => {
const formik = useFormik({
initialValues: {
username: "",
email: "",
password: "",
},
onSubmit,
validate,
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<h2>Log in</h2>
<label htmlFor='name'>Username</label>
<input
type='text'
id='username'
name='username'
{...formik.getFieldProps("username")}
/>
{formik.touched.username ? <p>{formik.errors.username}</p> : null}
<label htmlFor='email'>Email</label>
<input
type='email'
id='email'
name='email'
{...formik.getFieldProps("email")}
/>
{formik.touched.email ? (
<p className='text-red-500 mb-4 '>{formik.errors.email}</p>
) : null}
<label htmlFor='password'>Password</label>
<input
type='password'
id='password'
name='password'
{...formik.getFieldProps("password")}
/>
{formik.touched.password ? <p>{formik.errors.password}</p> : null}
<button type='submit'>REGISTER</button>
</form>
</div>
)};
For instance, you can create a validation schema using Yup and add it to the useFormik hook, as shown below:
import { object, string } from "yup";
const App = () => {
//👇🏻 Yup validation schema
const validationSchema = object().shape({
username: string()
.required("Username is required")
.min(6, "Username must be at least 6 characters"),
email: string().email("Invalid email").required("Email is required"),
password: string()
.required("Password is required")
.min(8, "Password must be at least 8 characters"),
});
//👇🏻 useFormik hook using the Yup validation schema
const formik = useFormik({
initialValues: {
username: "",
email: "",
password: "",
},
onSubmit: onSubmit,
validationSchema: validationSchema,
});
};
import { Form, Formik, Field, ErrorMessage } from "formik";const App = () => {
const initialValues = {
username: "",
email: "",
password: "",
};
return (
<div>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={validationSchema}
>
<Form>
<h2>Log in</h2>
<label htmlFor='name'>Username</label>
<Field type='text' id='username' name='username' />
<ErrorMessage name='username' render={(msg) => <p>{msg}</p>} />
<label htmlFor='email'>Email</label>
<Field type='email' id='email' name='email' />
<ErrorMessage name='email' render={(msg) => <p>{msg}</p>} />
<label htmlFor='password'>Password</label>
<Field type='password' id='password' name='password' />
<ErrorMessage name='password' render={(msg) => <p>{msg}</p>} />
<button type='submit'>REGISTER</button>
</Form>
</Formik>
</div>
);
};
Also published .