useReducer hook can be used to handle complex state management scenarios. Using regular state management becomes cumbersome when part of the state depends on other state values. useReducer simplify such state management by providing safe ways to access updated state values. This tutorial guides you through validating and submitting a form in Reactjs.
The code example shown is a component of a Reactjs project. You can use this code in an existing project or follow Reactjs official site to create a new Reactjs project.
Below is the complete code to validate and submit a form using the
import { useReducer } from "react"
type FormState = {
firstName: string
lastName: string
age: string
email: string
password: string
}
const initialState: FormState = {
firstName: "",
lastName: "",
age: "",
email: "",
password: ""
}
type FormValidityState = {
firstNameError: boolean
lastNameError: boolean
ageError: boolean
emailError: boolean
passwordError: boolean
isFormValid: boolean
}
const initialValidityState: FormValidityState = {
firstNameError: false,
lastNameError: false,
ageError: false,
emailError: false,
passwordError: false,
isFormValid: false
}
type FormAction = {
type: string
payLoad: string
}
type FormValidityAction = {
type: string
payLoad: FormState
}
const formReducer = (state: FormState, action: FormAction): FormState => {
switch(action.type){
case "UPDATE_FIRST_NAME": return{
...state, firstName: action.payLoad,
}
case "UPDATE_LAST_NAME": return{
...state,lastName: action.payLoad,
}
case "UPDATE_AGE": return{
...state, age: action.payLoad,
}
case "UPDATE_EMAIL": return{
...state, email: action.payLoad,
}
case "UPDATE_PASSWORD": return{
...state, password: action.payLoad,
}
default:
return state
}
}
const formValidityReducer = (state: FormValidityState, action: FormValidityAction): FormValidityState => {
let isValid: boolean = false;
switch(action.type){
case "VALIDATE_FIRST_NAME":
isValid = action.payLoad.firstName.length > 0 ? true: false
return{
...state,
...({firstNameError: !isValid, isFormValid: isValid && !state.lastNameError && !state.ageError && !state.emailError && !state.passwordError}),
}
case "VALIDATE_LAST_NAME":
isValid = action.payLoad.lastName.length > 0 ? true: false
return{
...state,
...({lastNameError: !isValid, isFormValid: isValid && !state.firstNameError && !state.ageError && !state.emailError && !state.passwordError})
}
case "VALIDATE_AGE":
isValid = action.payLoad.age.length > 0 ? true: false
return{
...state,
...({ageError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.emailError && !state.passwordError})
}
case "VALIDATE_EMAIL":
isValid = (action.payLoad.email.length > 0 && action.payLoad.email.includes("@")) ? true: false
return{
...state,
...({emailError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.ageError && !state.passwordError})
}
case "VALIDATE_PASSWORD":
isValid = action.payLoad.password.length > 9 ? true: false
return{
...state,
...({passwordError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.ageError && !state.emailError})
}
default:
return state
}
}
export const Form = () => {
const [formData, setFormData] = useReducer(formReducer, initialState)
const [formValidityData, setFormValidityData] = useReducer(formValidityReducer, initialValidityState)
const onButtonPress = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
console.log(formData)
//Form submission happens here
}
return(
<div style={STYLE.container}>
<form onSubmit={onButtonPress}>
<label style={STYLE.formElement} htmlFor="first_name">First Name</label>
<div style={STYLE.formElement}>
<input
id="first_name"
style={{backgroundColor:formValidityData.firstNameError ?"pink" : ""}}
onChange={(e) =>setFormData({type:"UPDATE_FIRST_NAME", payLoad:e.target.value})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_FIRST_NAME", payLoad: formData})}
type="text"/>
</div>
<label style={STYLE.formElement} htmlFor="last_name">Last Name</label>
<div style={STYLE.formElement}>
<input
id="last_name"
style={{backgroundColor:formValidityData.lastNameError ? "pink" : ""}}
onChange={(e) =>setFormData({type:"UPDATE_LAST_NAME", payLoad:e.target.value})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_LAST_NAME", payLoad: formData})}
type="text"/>
</div>
<label style={STYLE.formElement} htmlFor="last_name">Email</label>
<div style={STYLE.formElement}>
<input
id="email"
style={{backgroundColor:formValidityData.emailError ? "pink" : ""}}
onChange={(e) =>setFormData({type:"UPDATE_EMAIL", payLoad:e.target.value})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_EMAIL", payLoad: formData})}
type="text"/>
</div>
<label style={STYLE.formElement} htmlFor="last_name">Password</label>
<div style={STYLE.formElement}>
<input
id="password"
style={{backgroundColor:formValidityData.passwordError ? "pink" : ""}}
onChange={(e) =>setFormData({type:"UPDATE_PASSWORD", payLoad:e.target.value})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_PASSWORD", payLoad: formData})}
type="password"/>
</div>
<label style={STYLE.formElement} htmlFor="age">Age</label>
<div style={STYLE.formElement}>
<input
id="age"
style={{backgroundColor:formValidityData.ageError ? "pink" : ""}}
onChange={(e) =>setFormData({type:"UPDATE_AGE", payLoad:e.target.value})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_AGE", payLoad: formData})}
type="number"/>
</div>
<div style={STYLE.formElement}>
<input disabled={!formValidityData.isFormValid} type="submit" value={""+formValidityData.isFormValid}/>
</div>
</form>
</div>
)
}
const STYLE = {
container: {
borderRadius: "5px",
backgroundColor: "#f2f2f2",
padding: "20px",
maxWidth:"240px"
},
formElement: {
padding: "6px 24px"
}
}
Note that in each validation case, we depend on multiple state variables. For example, to validate the
const formValidityReducer = (state: FormValidityState, action: FormValidityAction): FormValidityState => {
let isValid: boolean = false;
switch(action.type){
case "VALIDATE_FIRST_NAME":
isValid = action.payLoad.firstName.length > 0 ? true: false
return{
...state,
...({firstNameError: !isValid, isFormValid: isValid && !state.lastNameError && !state.ageError && !state.emailError && !state.passwordError}),
}
case "VALIDATE_LAST_NAME":
isValid = action.payLoad.lastName.length > 0 ? true: false
return{
...state,
...({lastNameError: !isValid, isFormValid: isValid && !state.firstNameError && !state.ageError && !state.emailError && !state.passwordError})
}
case "VALIDATE_AGE":
isValid = action.payLoad.age.length > 0 ? true: false
return{
...state,
...({ageError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.emailError && !state.passwordError})
}
case "VALIDATE_EMAIL":
isValid = (action.payLoad.email.length > 0 && action.payLoad.email.includes("@")) ? true: false
return{
...state,
...({emailError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.ageError && !state.passwordError})
}
case "VALIDATE_PASSWORD":
isValid = action.payLoad.password.length > 9 ? true: false
return{
...state,
...({passwordError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.ageError && !state.emailError})
}
default:
return state
}
}
In each case statement, we use the newly created
const formReducer = (state: FormState, action: FormAction): FormState => {
switch(action.type){
case "UPDATE_FIRST_NAME": return{
...state, firstName: action.payLoad,
}
case "UPDATE_LAST_NAME": return{
...state,lastName: action.payLoad,
}
case "UPDATE_AGE": return{
...state, age: action.payLoad,
}
case "UPDATE_EMAIL": return{
...state, email: action.payLoad,
}
case "UPDATE_PASSWORD": return{
...state, password: action.payLoad,
}
default:
return state
}
}
We use two state objects to keep form data separate from validation data. You can achieve the same result with a single state object and filter the validation properties before submitting the form, keeping the response clean.
import { useReducer } from "react"
const initialState = {
firstName: "",
lastName: "",
age: "",
email: "",
password: ""
}
const initialValidityState = {
firstNameError: false,
lastNameError: false,
ageError: false,
emailError: false,
passwordError: false,
isFormValid: false
}
const formReducer = (state, action) => {
const {name, value} = action.type
return{
...state, [name]: value,
}
}
const formValidityReducer = (state, action) => {
let isValid = false;
switch(action.type){
case "VALIDATE_FIRST_NAME":
isValid = action.payLoad.firstName.length > 0 ? true: false
return{
...state,
...({firstNameError: !isValid, isFormValid: isValid && !state.lastNameError && !state.ageError && !state.emailError && !state.passwordError}),
}
case "VALIDATE_LAST_NAME":
isValid = action.payLoad.lastName.length > 0 ? true: false
return{
...state,
...({lastNameError: !isValid, isFormValid: isValid && !state.firstNameError && !state.ageError && !state.emailError && !state.passwordError})
}
case "VALIDATE_AGE":
isValid = action.payLoad.age.length > 0 ? true: false
return{
...state,
...({ageError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.emailError && !state.passwordError})
}
case "VALIDATE_EMAIL":
isValid = (action.payLoad.email.length > 0 && action.payLoad.email.includes("@")) ? true: false
return{
...state,
...({emailError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.ageError && !state.passwordError})
}
case "VALIDATE_PASSWORD":
isValid = action.payLoad.password.length > 9 ? true: false
return{
...state,
...({passwordError: !isValid, isFormValid: isValid && !state.firstNameError && !state.lastNameError && !state.ageError && !state.emailError})
}
default:
return state
}
}
export const Form = () => {
const [formData, setFormData] = useReducer(formReducer, initialState)
const [formValidityData, setFormValidityData] = useReducer(formValidityReducer, initialValidityState)
const onButtonPress = (event) => {
event.preventDefault()
console.log(formData)
//Form submission happens here
}
return(
<div style={STYLE.container}>
<form onSubmit={onButtonPress}>
<label style={STYLE.formElement} htmlFor="first_name">First Name</label>
<div style={STYLE.formElement}>
<input
name="firstName"
style={{backgroundColor:formValidityData.firstNameError ?"pink" : ""}}
onChange={(e) =>setFormData({type:e.target})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_FIRST_NAME", payLoad: formData})}
type="text"/>
</div>
<label style={STYLE.formElement} htmlFor="last_name">Last Name</label>
<div style={STYLE.formElement}>
<input
name="lastName"
style={{backgroundColor:formValidityData.lastNameError ? "pink" : ""}}
onChange={(e) =>setFormData({type:e.target})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_LAST_NAME", payLoad: formData})}
type="text"/>
</div>
<label style={STYLE.formElement} htmlFor="last_name">Email</label>
<div style={STYLE.formElement}>
<input
name="email"
style={{backgroundColor:formValidityData.emailError ? "pink" : ""}}
onChange={(e) =>setFormData({type:e.target})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_EMAIL", payLoad: formData})}
type="text"/>
</div>
<label style={STYLE.formElement} htmlFor="last_name">Password</label>
<div style={STYLE.formElement}>
<input
name="password"
style={{backgroundColor:formValidityData.passwordError ? "pink" : ""}}
onChange={(e) =>setFormData({type:e.target})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_PASSWORD", payLoad: formData})}
type="password"/>
</div>
<label style={STYLE.formElement} htmlFor="age">Age</label>
<div style={STYLE.formElement}>
<input
name="age"
style={{backgroundColor:formValidityData.ageError ? "pink" : ""}}
onChange={(e) =>setFormData({type:e.target})}
onBlur={(e) => setFormValidityData({type: "VALIDATE_AGE", payLoad: formData})}
type="number"/>
</div>
<div style={STYLE.formElement}>
<input disabled={!formValidityData.isFormValid} type="submit" value={""+formValidityData.isFormValid}/>
</div>
</form>
</div>
)
}
const STYLE = {
container: {
borderRadius: "5px",
backgroundColor: "#f2f2f2",
padding: "20px",
maxWidth:"240px"
},
formElement: {
padding: "6px 24px"
}
}