Creating a Controlled Form with React Hooks
One Way To Do It
Table of Contents
I. A Quick Recap of Using Class Components for Controlled Forms
- In the “input”
- In the "handleChange" function
- Setting State Dynamically with “handleChange”
- Form Submission with “handleSubmit”
II. A Non-Dynamic Controlled Form with React Hooks
III. A Dynamic Controlled Form with React Hooks
If you learned to make controlled form components using a class components in React, you may have used a setup like this:
import React, { Component } from 'react'class RegistrationForm from Component { state = {
name: '',
email: '',
password: ''
} handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
} handleSubmit = event => {
event.preventDefault();
processSubmittedDataFunction(this.state);
this.setState({
name: '',
email: '',
password: ''
})
}render(){
return(
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
type="text"
name="name"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
<label>
Email:
<input
type="email"
name="email"
value={this.state.email}
onChange={this.handleChange}
/>
</label>
<label>
Password:
<input
type="password"
name="password"
value={this.state.password}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
)
}
}export default RegistrationForm
A Quick Recap of Using Class Components for Controlled Forms
Here, I’ve made the RegistrationForm
component itself a class component and set up state and set initial state to empty strings for each piece of state:
state = {
name: '',
email: '',
password: ''
}
As a user fills out the form, the change in the form’s input values are updated through the handleChange
function:
In the "input”
:
<input
type="text"
name="name"
value={this.state.name}
onChange={this.handleChange}
/>
In the "handleChange”
function:
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
Setting State Dynamically with “handleChange”
What is happening here is that on every change to the form—when the user types:
- We have an event listener trigger the
handleChange
function (onChange={this.handleChange}
). - This
handleChange
function grabs the input name and value from theevent
. The event’s name is accessible viaevent.target.name
, the value, byevent.target.value
.
The setState
method then can be used to set the different pieces of state dynamically.
- If the input for “email” is being filled out by a user, for example, the input that has its name set to
email
triggers thehandleChange
function as one types. - The
event.target.name
is “email” and theevent.target.value
is the value that the user types into the email input field on the page.
The same is true for any of the other input fields. This is possible since the key for any value we want to set in the setState
function is set up to be dynamic.
In a JavaScript object, we can set an object’s key dynamically if we wrap the variable we want to use as the key name in square brackets(
[variableKeyName]
).
Form Submission with “handleSubmit”
When the submit
input is clicked the handleSubmit
function is invoked. (Note that the onSubmit={handleSubmit}
is set in the opening form
tag, not in the input
field for the submit button.)
If we are building an SPA (Single-Page Application), we do not want to the page to be refreshed at form submission.
- To prevent a page refresh, we use the
event
parameter and callevent.preventDefault()
inside ourhandleSubmit
function.
We want to set up the function we will use to handle the submitted data here as well. In this example, I have made a placeholder function called processSubmittedDataFunction
. Since we want to use the data that has been set in all of the fields, we can use the current state of each field at the time of submission, accessing state via this.state
, and pass that into our function:
processSubmittedDataFunction(this.state);
Finally, be sure to clear out the fields after submission by resetting the state of each field:
this.setState({
name: '',
email: '',
password: ''
})
A Non-Dynamic Controlled Form with React Hooks
The useState
React hook allows the use of state features in functional components, without necessary recourse to a class component construction. But, the setup for the useState
hook often leads new hook users to split up each piece of state using individual hooks that then require separate handleChange
functions.
import { useState } from 'react'const RegistrationForm = props => {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('') const handleNameChange = event => {
setName(event.target.value)
} const handleEmailChange = event => {
setemail(event.target.value)
} const handlePasswordChange = event => {
setPassword(event.target.value)
}handleSubmit = event => {
event.preventDefault();
const formData={
name,
email,
password
}
processSubmittedDataFunction(formData);
setName('')
setEmail('')
setPassword('')
} return(
<form onSubmit={handleSubmit}>
<label>
Name:
<input
type="text"
name="name"
value={name}
onChange={handleNameChange}
/>
</label>
<label>
Email:
<input
type="email"
name="email"
value={email}
onChange={handleEmailChange}
/>
</label>
<label>
Password:
<input
type="password"
name="password"
value={password}
onChange={handlePasswordChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
)
}
Handling State Separately
Note that we now have more code since we have to handle each piece of state separately using different state handling hook functions (i.e. setName
, setEmail
, setPassword
).
- We have separate
handleChange
functions throughout for each piece of state handled by auseState
hook:
const handleNameChange = event => {
setName(event.target.value)
}const handleEmailChange = event => {
setemail(event.target.value)
}const handlePasswordChange = event => {
setPassword(event.target.value)
}
Repackaging State in Submission
In our handleSubmit
function we also need to repackage our formData. Here, we have taken advantage of ES6 syntax, which allows us to set the key and value in an object with a single key term, if the variable name for each is the same:
formData = {
name,
email,
password
}
- This is equivalent to:
formData = {
name: name,
email: email,
password: password
}
where the values are being read from the useState hooks state values of name
, email
, and password
.
Is there a better way?
A Dynamic Controlled Form with React Hooks
There is, I submit, a better way. You just need to set up your state and useState
hook a little differently.
import { useState } from 'react'const RegistrationForm = props => {
const [formData, setFormData] = useState({
name: '',
email: '',
password: ''
})
const handleChange = event => {
setFormData({
...formData,
[event.target.name]: event.target.value
})
}handleSubmit = event => {
event.preventDefault();
processSubmittedDataFunction(formData);
setFormData({
name: '',
email: '',
password: ''
})
}return(
<form onSubmit={handleSubmit}>
<label>
Name:
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
</label>
<label>
Email:
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</label>
<label>
Password:
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
)
}
One “useState”
to Rule Them All!
We can use one useState
hook if we set up a single piece of state, here called formData
, with each input field as a key-value pair in that state:
const [formData, setFormData] = useState({
name: '',
email: '',
password: ''
})
A Dynamic “handleChange”
function
const handleChange = event => {
setFormData({
...formData,
[event.target.name]: event.target.value
})
}
Note that we use the spread operator (…) to pass in the previous state each time we change some element of the formData
. ( {...formData}
).
- This allows us to maintain changes in
name
andemail
, for instance, as we chance the value ofpassword
.
And now we can set each piece of state dynamically as we did in the example above with a class component:
[event.target.name]: event.target.value
And we only need one handleChange
function for each input.
Setting the Value for Each “input”
In setting the value of each input in the controlled form, we now need to access each piece of state from formData
:
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
Submitting the Data in One Go
Finally, when we submit the data, we do not need to repackage each piece of state. We can just send over all of the state at once, since it is all maintained as formData
.
handleSubmit = event => {
event.preventDefault();
processSubmittedDataFunction(formData);
setFormData({
name: '',
email: '',
password: ''
})
}
After that, remember to reset the state back to blank strings too:
handleSubmit = event => {
event.preventDefault();
processSubmittedDataFunction(formData);
setFormData({
name: '',
email: '',
password: ''
})
}
And that is one way to create a dynamic controlled form with React hooks!