This article discusses five ways to submit a form in Reactjs. Each method prepares an Html form data object for submission. We use different ways to maintain the form data using state management in each method we discuss.
Feel free to select a suitable submission method depending on your requirements. Below is an example of sending the form object to the server as a JSON response.
fetch('saveContact/', {
method: 'POST',
body: JSON.stringify(responseBody),
headers: {
'Content-Type': 'application/json'
},
})
1. Reactjs form submit with multiple useState hooks
In this method, we maintain each form input element value in a separate state variable. That is the simplest out of the five different methods we discuss. Suitable for simple forms with a few input elements that need simple form validation.
import { useState } from "react"
export const Form = () => {
const [firstName, setFirstName] = useState("")
const [lastName, setLastName] = useState("")
const [age, setAge] = useState("")
interface FormDataType {firstName:string, lastName: string, age: string}
const responseBody: FormDataType = {firstName: "", lastName: "", age: "0"}
const onSubmitHandler = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
responseBody.firstName = firstName
responseBody.lastName = lastName
responseBody.age = age
console.log(JSON.stringify(responseBody))
//Form submission happens here
}
const inputChangeHandler = (setFunction: React.Dispatch<React.SetStateAction<string>>, event: React.ChangeEvent<HTMLInputElement>) => {
setFunction(event.target.value)
}
return(
<form onSubmit={onSubmitHandler}>
<div><label htmlFor="first_name">First Name</label></div>
<div><input id="first_name" onChange={(e)=>inputChangeHandler(setFirstName, e)} type="text"/></div>
<div><label htmlFor="last_name">Last Name</label></div>
<div><input id="last_name" onChange={(e)=>inputChangeHandler(setLastName, e)} type="text"/></div>
<div><label htmlFor="age">Age</label></div>
<div><input id="age" onChange={(e)=>inputChangeHandler(setAge, e)} type="number"/></div>
<input type="submit"/>
</form>
)
}
2. Reactjs form submit with single useState hook
Here we store all the form input data in a single state variable. To achieve that, we have to build an object with properties to represent all the input elements in the form. This object can be stored in a state variable. Each input change updates the relevant property of the object stored in the state variable.
import { useState } from "react"
export const Form = () => {
interface FormDataType {firstName:string, lastName: string, age: string}
const formData: FormDataType = {firstName: "", lastName: "", age: ""}
const [responseBody, setResponseBody] = useState<FormDataType>(formData)
const inputChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
const {name, value} = event.target
setResponseBody({...responseBody, [name]: value})
}
const onSubmitHandler = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
console.log(responseBody)
//Form submission happens here
}
return(
<form onSubmit={onSubmitHandler}>
<div><label htmlFor="firstName">First Name</label></div>
<div><input id="firstName" name="firstName" onChange={(e)=>inputChangeHandler(e)} type="text"/></div>
<div><label htmlFor="lastName">Last Name</label></div>
<div><input id="lastName" name="lastName" onChange={(e)=>inputChangeHandler(e)} type="text"/></div>
<div><label htmlFor="age">Age</label></div>
<div><input id="age" name="age" onChange={(e)=>inputChangeHandler(e)} type="number"/></div>
<input type="submit"/>
</form>
)
}
3. Using useRef to maintain form element values
The useRef can maintain a reference to the form element's dom. Since the useRef directly accesses the real dom, it is not advised to use useRef to do any direct dom manipulations. However, in our case, we only read from the dom, therefore, no performance impact is anticipated.
import { useRef } from "react"
export const Form = () => {
const inputFirstName = useRef<HTMLInputElement>(null)
const inputLastName = useRef<HTMLInputElement>(null)
const inputAge = useRef<HTMLInputElement>(null)
interface FormDataType {firstName:string, lastName: string, age: string}
const formData: FormDataType = {firstName: "", lastName: "", age: ""}
const onSubmitHandler = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
formData.firstName = inputFirstName?.current?.value||""
formData.lastName = inputLastName?.current?.value||""
formData.age = inputAge?.current?.value||""
console.log(formData)
//Form submission happens here
}
return(
<form onSubmit={onSubmitHandler}>
<div><label htmlFor="first_name">First Name</label></div>
<div><input id="first_name" ref={inputFirstName} type="text"/></div>
<div><label htmlFor="last_name">Last Name</label></div>
<div><input id="last_name" ref={inputLastName} type="text"/></div>
<div><label htmlFor="age">Age</label></div>
<div><input id="age" ref={inputAge} type="number"/></div>
<input type="submit"/>
</form>
)
}
4. Using FormData to read Html Form elements
In this method, we cast the HtmlForm element to the FormData object. Then we iterate the FormData object and create the responseBody with key and value pairs with respective Html element names and values. For more details about FormObject, check the Mozilla Developer site.
export const Form = () => {
interface formDataType {[key:string]: FormDataEntryValue}
const responseBody: formDataType = {}
const inputChangeHandler = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const formData = new FormData(event.currentTarget as HTMLFormElement)
formData.forEach((value, property:string) => responseBody[property] = value);
console.log(JSON.stringify(responseBody))
//Form submission happens here
}
return(
<form onSubmit={inputChangeHandler}>
<div><label htmlFor="firstName">First Name</label></div>
<div><input id="firstName" name="firstName" type="text"/></div>
<div><label htmlFor="lastName">Last Name</label></div>
<div><input id="lastName" name="lastName" type="text"/></div>
<div><label htmlFor="age">Age</label></div>
<div><input id="age" name="age" type="number"/></div>
<input type="submit"/>
</form>
)
}
5. Using useReducer to perform advanced validation on Reactjs forms
If your form needs complex validation and pre-submit processing logic, useReducer can be a better option. Using useReducer is a bit complex, but it offers a few advantages when accessing the form state. If you need to access the previous state of a variable, useReducer state provides safer access to the previous state. useReducer provides safer access to a shared state where you can perform validation and any pre-processing logic.
import { useReducer } from "react"
type FormState = {
firstName: string
lastName: string
age: string
}
type FormAction = {
type: string
payLoad: string
}
const initialState: FormState = {
firstName: "",
lastName: "",
age: "",
}
type FormValidityState = {
firstNameError: boolean
lastNameError: boolean
ageError: boolean
}
const initialValidityState: FormValidityState = {
firstNameError: false,
lastNameError: false,
ageError: false
}
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,
}
default:
return state
}
}
const formValidityReducer = (state: FormValidityState, action: FormValidityAction): FormValidityState => {
switch(action.type){
case "VALIDATE_FIRST_NAME": return{
...state,
...({firstNameError: action.payLoad.firstName.length > 0 ? false: true})
}
case "VALIDATE_LAST_NAME": return{
...state,
...({lastNameError: action.payLoad.lastName.length > 0 ? false: true})
}
case "VALIDATE_AGE": return{
...state,
...({ageError: action.payLoad.age.length > 0 ? false: true})
}
default:
return state
}
}
export const Form = () => {
const [formData, setFormData] = useReducer(formReducer, initialState)
const [formValidityData, setFormValidityData] = useReducer(formValidityReducer, initialValidityState)
const onButtonPress = (event: React.FormEvent) => {
event.preventDefault()
console.log(formData)
//Form submission happens here
}
return(
<form onSubmit={onButtonPress}>
<div><label htmlFor="first_name">First Name</label></div>
<div>
<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>
<div><label htmlFor="last_name">Last Name</label></div>
<div>
<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>
<div><label htmlFor="age">Age</label></div>
<div>
<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>
<input disabled={formValidityData.firstNameError===true || formValidityData.lastNameError===true || formValidityData.ageError===true} type="submit"/>
</form>
)
}