React is great for interactive applications. Besides client side state, forms are one of the most important concepts for interactive applications. There are many libraries like Formik to create forms. But to understand these libraries, it’s important to understand the basics of react forms.
Form that we want to create
To show the basic concepts for forms in react, we will develop the following example. It contains different form fields like text, radio buttons and selects.
You can find the basic form written in jsx in the this codestandbox. It’s not part of this post, but please use at least basic html elements to create an accessible form. Every field should have a label and your submit button should be a button and not a div with an onClick-handler.
In react you can use uncontrolled or controlled components to handle the state in your forms.
Uncontrolled Components
In uncontrolled components you don’t save the state of your form in react, but it is saved in the dom-elements. You don’t need useState, useReducer or any other hook to save the state.
export default function App() {
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
for (const [key, value] of formData.entries()) {
console.log(key, value);
}
};
return (
<div className="container">
<form className="my-3" onSubmit={(e) => handleSubmit(e)}>
...
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email address:*
</label>
<input
type="email"
className="form-control"
id="email"
name="email"
required
/>
</div>
...
<div className="mb-3">
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
);
}
In this example we set a onSubmit-handler and get the form data from the form element with to FormData API. It’s important to name all fields, then the key in the for loop contains the name and the value the entered value. With the FormData API we can send it straight to an API or convert it into a object or into json or ….
You can get the data of uncontrolled components from the event or you can use useRef, to save the DOM-elements and extract the content from the saved elements.
Uncontrolled components aren’t well integrated into the state management of react. You mostly want to use controlled components. Uncontrolled components can be handy if you want to integrate non react code into react. But this are rare cases.
Controlled Components
With controlled components you save the state of the form in react. Therefore you can use useState, useReducer or any other state management concept. In this example we save the state with useState. On any change of the input field the handleChange function will be called and the content of the field will be written into the state. We extract the name and the value from the field, therefore we can use one function for all form fields.
When the user clicks the submit button, the handleSubmit function will be called and we can use the state in our formData variable.
import { useState } from "react";
export default function App() {
const [formData, setFormData] = useState({
email: "",
});
const handleChange = (event) => {
const name = event.target.name;
const value = event.target.value;
setFormData((values) => ({ ...values, [name]: value }));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<div className="container">
<form className="my-3" onSubmit={(e) => handleSubmit(e)}>
...
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email address:*
</label>
<input
type="email"
className="form-control"
id="email"
name="email"
required
onChange={handleChange}
/>
</div>
...
<div className="mb-3">
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
);
}
Controlled components are well integrated in react and you can use the well known state management concepts.
input type=”file”
There is always one exception. If you want to use the input type file you must use uncontrolled components. You can build your complete form in uncontrolled components, but you can also only use the one file field uncontrolled.
In this example you can select an image and the image will be shown when you click the submit button,
import "bootstrap/dist/css/bootstrap.min.css";
import { useState } from "react";
export default function App() {
const [image, setImage] = useState(null);
const handleSubmit = (e) => {
e.preventDefault();
if (e.target.elements.image.files.length > 0) {
var reader = new FileReader();
reader.onload = function () {
setImage(reader.result);
};
reader.readAsDataURL(e.target.elements.image.files[0]);
}
};
return (
<div className="container">
<form onSubmit={(e) => handleSubmit(e)}>
<div className="mb-3">
<label htmlFor="image" className="form-label">
Image:
</label>
<input type="file" className="form-control" id="image" name="image" />
</div>
<div className="mb-3">
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
{image && <img src={image} alt="" />}
</div>
);
}
In the handleSubmit function we use the FileReader-API to get the content of the image as dataURL. We put this dataURL into our state variable image. When the image variable is filled, we show the image.
You can use the FileReader-API to get the content of the file in different formats (text, blob, dataURL, …)
Summary
- Know the differences between controlled and uncontrolled components
- Input type=file must be used uncontrolled.
- Build accessible forms. (meaningful labels, error messages, feedback)
Top comments (0)