Handling Forms in React
How to handle forms in a React application
Forms are one of the few HTML elements that are interactive by default.
They were designed to allow the user to interact with a page.
Common uses of forms?
- Search
- Contact forms
- Shopping carts checkout
- Login and registration
- and more!
Using React we can make our forms much more interactive and less static.
There are two main ways of handling forms in React, which differ on a fundamental level: how data is managed.
- if the data is handled by the DOM, we call them uncontrolled components
- if the data is handled by the components we call them controlled components
As you can imagine, controlled components is what you will use most of the time. The component state is the single source of truth, rather than the DOM. But sometimes you are forced to use uncontroller components, for example when using some form fields that are inherently uncontrolled because of their behavior, like the <input type="file">
field.
When an element state changes in a form field managed by a component, we track it using the onChange
attribute.
class Form extends React.Component {
constructor(props) {
super(props)
this.state = { username: '' }
}
handleChange(event) {}
render() {
return (
<form>
Username:
<input
type="text"
value={this.state.username}
onChange={this.handleChange}
/>
</form>
)
}
}
With class components, in order to set the new state, we must bind this
to the handleChange
method, otherwise this
is not accessible from within that method:
class Form extends React.Component {
constructor(props) {
super(props)
this.state = { username: '' }
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({ username: event.target.value })
}
render() {
return (
<form>
<input
type="text"
value={this.state.username}
onChange={this.handleChange}
/>
</form>
)
}
}
Similarly, we use the onSubmit
attribute on the form to call the handleSubmit
method when the form is submitted:
class Form extends React.Component {
constructor(props) {
super(props)
this.state = { username: '' }
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(event) {
this.setState({ username: event.target.value })
}
handleSubmit(event) {
alert(this.state.username)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.username}
onChange={this.handleChange}
/>
<input type="submit" value="Submit" />
</form>
)
}
}
Using hooks it’s all much simpler:
const Form = props => {
const [username, setUsername] = useState()
const handleChangeUsername = e => {
setUsername(e.target.value)
}
const handleSubmit = event => {
alert(username)
event.preventDefault()
}
render() {
return (
<form onSubmit={handleSubmit}>
Username:
<input
type="text"
value={username}
onChange={handleChangeUsername}
/>
</form>
)
}
}
Validation in a form can be handled in the handleChange
method: you have access to the old value of the state, and the new one. You can check the new value and if not valid reject the updated value (and communicate it in some way to the user).
HTML Forms are inconsistent. They have a long history, and it shows. React however makes things more consistent for us, and you can get (and update) fields using its value
attribute.
Here’s a textarea
, for example:
<textarea value={this.state.address} onChange={this.handleChange} />
The same goes for the select
tag:
<select value="{this.state.age}" onChange="{this.handleChange}">
<option value="teen">Less than 18</option>
<option value="adult">18+</option>
</select>
Previously we mentioned the <input type="file">
field. That works a bit differently.
In this case you need to get a reference to the field by assigning the ref
attribute to a property defined in the constructor with React.createRef()
, and use that to get the value of it in the submit handler:
class FileInput extends React.Component {
constructor(props) {
super(props)
this.curriculum = React.createRef()
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit(event) {
alert(this.curriculum.current.files[0].name)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="file" ref={this.curriculum} />
<input type="submit" value="Submit" />
</form>
)
}
}
This is the uncontrolled components way. The state is stored in the DOM rather than in the component state (notice we used this.curriculum
to access the uploaded file, and have not touched the state
.
I know what you’re thinking - beyond those basics, there must be a library that simplifies all this form handling stuff and automates validation, error handling and more, right? There is a great one, Formik.
→ I wrote 17 books to help you become a better developer:
- C Handbook
- Command Line Handbook
- CSS Handbook
- Express Handbook
- Git Cheat Sheet
- Go Handbook
- HTML Handbook
- JS Handbook
- Laravel Handbook
- Next.js Handbook
- Node.js Handbook
- PHP Handbook
- Python Handbook
- React Handbook
- SQL Handbook
- Svelte Handbook
- Swift Handbook
Also, JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025