Form validation using custom hooks in react/typescript
Form validation is a chore that every react developer has to contend with in their job, this is why there are many libraries that hide the pain of validating inputs behind beautiful abstractions. The form validation libraries offer plenty of functionality and should be the go to option for react devs who need to ship stuff fast, however, it does not hurt to learn how to create form validation from scratch. In this article I demonstrate how to add form validation using a custom hook.
The form is set up such that when the user clicks submit, they are presented with all the errors, and the form cannot be submitted until the errors are fixed.
The core part of the validation is this function
export interface Ivalues{
username: string,
email: string,
password: string,
password2: string
}
export default function validateInfo(values:Ivalues){
const errors: Partial<Ivalues> = {}
if(!values.username.trim()){
errors.username = "Username Required"
}
if(!values.email){
errors.email = "Email Required"
}else if(!/\S+@\S+\.\S+/.test(values.email)){
errors.email = "Email address provided is invalid"
}
if(!values.password){
errors.password = "Password is required"
}else if(values.password.length < 6){
errors.password = "Password needs to be six characters or more"
}
if(!values.password2){
errors.password2 = "Password is required"
}else if(values.password !== values.password2){
errors.password2 = "Passwords do not match"
}
return errors
}
The function takes in an object representing form inputs, in this case it accepts username, email, password and password2 for confirming password. A series of if statements check if the inputs are present and then performs some validation rules. If the inputs have errors, the name of the input and the error is appended to an object that is returned by the function.
Next we have the hook, which imports the validation function above and then performs the validation whenever the user clicks submit.
import React, { useState } from "react";
import validateInfo from "./validateinfo";
const useForm = () =>{
const [values, setValues] = useState({
username:"",
email:"",
password:"",
password2:""
})
const [errors, setErrors] = useState<Partial<Ivalues>>({})
const [isSubmitting, setIsSubmitting] = useState(false)
const handlechange = (e:React.ChangeEvent<HTMLInputElement>) =>{
const { name, value } = e.target
setValues({...values, [name]: value})
}
const handleSubmit = (e:React.FormEvent)=>{
e.preventDefault()
setErrors(validateInfo(values))
if(Object.keys(validateInfo(values)).length === 0){
setIsSubmitting(true)
try {
// submit to backend
console.log("SUBMITTED!!! ")
} catch (error) {
console.log("Could not submit form")
} finally{
setIsSubmitting(false)
}
}
}
return { handlechange, handleSubmit, values, errors, isSubmitting }
}
export default useForm
The hook, maintains the state for the form inputs and submits the form inputs for validation whenever handlesubmit is triggered when the user clicks the sign up button. The hook returns
handlechange: for updating state when form inputs change,
handlesubmit: triggered when the user submits the form, this function then passes the form inputs to the validating function and then stores any errors in the error state
Values: this is the state to hold the form inputs
errors: any errors in the form inputs are placed in this state, and the component that uses useForm hook can display the errors to the user
isSubmitting: this signals to components that the form is in a submitting state, so that the components can implement logic for loading indicators etc
Finally we have the component that displays the form to the user, this component imports the useForm hook. and uses output from the hook to display errors and submit the form
import useForm from "./useForm"
const FormSignup = () => {
const { handleSubmit, handlechange, values, errors } = useForm()
return (
<div className="form-content-right">
<form className="form">
<h1>
Get started with us today, create your account by filling the information
below
</h1>
<div className="form-inputs">
<label htmlFor="username" className="form-label"> User Name</label>
<input
id="username"
type="text"
className="form-input"
name="username"
placeholder="Enter your username"
value={values.username}
onChange={handlechange}
/>
{errors.username && <p>{errors.username}</p>}
</div>
<div className="form-inputs">
<label htmlFor="email" className="form-label"> Email</label>
<input
id="email"
type="email"
className="form-input"
name="email"
placeholder="Enter your email"
value={values.email}
onChange={handlechange}
/>
{errors.email && <p>{errors.email}</p>}
</div>
<div className="form-inputs">
<label htmlFor="password" className="form-label"> Password</label>
<input
id="password"
type="password"
className="form-input"
name="password"
placeholder="Enter your password"
onChange={handlechange}
value={values.password}
/>
{errors.password && <p>{errors.password}</p>}
</div>
<div className="form-inputs">
<label htmlFor="password2" className="form-label"> Confirm Password</label>
<input
id="password2"
type="password2"
className="form-input"
name="password2"
placeholder="Confirm password"
onChange={handlechange}
value={values.password2}
/>
{errors.password2 && <p>{errors.password2}</p>}
</div>
<button
className="form-input-btn"
type="submit"
onClick={handleSubmit}
>
Sign Up
</button>
<span className="form-input-login">
Already have an account? Login <a href="#">Here</a>
</span>
</form>
</div>
)
}
export default FormSignup
With a bit of css you can display the useful error message to users, powered by the custom hook!