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 handleChangefunction ( onChange={this.handleChange}).
  • This handleChangefunction grabs the input name and value from the event. The event’s name is accessible via event.target.name, the value, by event.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 the handleChange function as one types.
  • The event.target.name is “email” and the event.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 formtag, 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 call event.preventDefault() inside our handleSubmit 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 useStatehook often leads new hook users to split up each piece of state using individual hooks that then require separate handleChangefunctions.

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 handleChangefunctions throughout for each piece of state handled by a useStatehook:
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 and email, for instance, as we chance the value of password.

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 handleChangefunction 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!

--

--

David Ryan Morphew

I’m very excited to start a new career in Software Engineering. I love the languages, frameworks, and libraries I’ve already learned / worked with (Ruby, Rails,