Forms, oh forms. They're often dreaded by front-end developers, and for good reason. Building forms can be a tedious and challenging task. But despite their notoriety, forms are an essential component of web development. They're ubiquitous, appearing on nearly every website, including the likes of Google.com, which is essentially just a form. While the React ecosystem boasts an array of packages that claim to simplify form handling, I believe that they're often unnecessary. In fact, forms in React aren't as daunting as they may seem! In this article, we'll explore how React's capabilities, particularly with the use of hooks like useState, can make working with forms a breeze. Say goodbye to form dread and let's dive into the world of form handling in React!
Data Binding
As web developers, we frequently encounter the need to synchronize state with form inputs in our applications. For instance, we might want to bind the value of a "username" field to a corresponding state variable that holds the username data. This is commonly referred to as "data binding", and many front-end frameworks provide mechanisms to achieve this. In React, this can be achieved with a straightforward syntax, as shown below:
In React, the value attribute works differently than in HTML.
In HTML, value sets the default value and allows editing.
In React, value locks the input to the specified value, making it read-only.
By setting value to our searchTerm state variable, we ensure that the input always displays the search term.
Adding a button that sets the search term to a random number shows that the input updates to reflect the new value.
However, the data binding is only one-way. The input displays the state value, but the state cannot be changed by the input alone.
To persist edits, we can call setSearchTerm with the input's current value, and React will update the input to reflect the updated state after re-rendering.
While the onChange listener alone seems to work for updating the state, it creates a one-way data binding where the input can update the state but the state cannot update the input.
Similarly, if the state has an initial value, it won't be displayed unless we control the input by setting value={searchTerm}. Overall, understanding how
value
andonChange
work together is crucial for effective data binding in React forms. So, let's make sure to utilize them appropriately in our applications. Enjoy coding!Controlled vs. Uncontrolled inputs
In the world of React, when we specify the value attribute on a form control, we are essentially telling React to take control of that input element. This is known as a "controlled" element in React's terminology, where React manages the input and its value.
On the other hand, if we omit the value attribute, the input becomes an "uncontrolled" element. This means that React doesn't manage the input and its value in any way.
It's important to keep in mind the golden rule here: an element should always be either controlled or uncontrolled, but not both. React doesn't appreciate it when we switch an element's status from one to the other, as it can lead to potential issues.
Let's dive into a common pitfall that can arise in such situations and learn how to avoid it.
Consider the following scenario:
If we try typing in the text input, and then switch to the “Console” tab. We should encounter a warning that begins like this:
Warning: A component is changing an uncontrolled input to be controlled.
But why? The input is controlled! We're setting value={username}
from the very first render.
Well, the problem is that the username is undefined at first, since there is no default value in the state hook.
In React, when we set the value
of a form control to undefined
, it behaves the same as if we didn't set it at all. As a result, React treats the input as an uncontrolled element. However, when the user starts typing in the input and the onChange
event is triggered, updating the value of username
from undefined
to a string, React flips the element to a controlled element and raises a warning.
Here's how to solve the problem: We always want to make sure we're passing a defined value. We can do this by initializing username to an empty string:
// 🚫 Incorrect. username will flip from undefined to a string:
const [username, setUsername] = React.useState();
// ✅ Correct. username will always be a string:
const [username, setUsername] = React.useState('');
The onClick dilemma
Now developers may be tempted to use divs
instead of form
tag as many don't really like forms
and hence instinctively solve this by adding an onClick
handler to the submit button:
<button onClick={() => runSearch(searchTerm)}>
Search!
</button>
With this solution, the user can only submit form by clicking the "Search" button.
So the question stands, why use something out of the way and re-implementing stuff that the browser already knows how to do?
Use "form" tag
form
tag is the answer to our problem and then, instead of listening for clicks and keys, we can listen for the form submit event.
import React from 'react';
function SearchForm({ runSearch }) {
const [searchTerm, setSearchTerm] = React.useState('');
return (
<form
className="search-form"
onSubmit={event => {
event.preventDefault();
runSearch(searchTerm);
}}
>
<input
type="text"
value={searchTerm}
onChange={event => {
setSearchTerm(event.target.value);
}}
/>
<button>
Search!
</button>
</form>
);
}
export default SearchForm;
In order to trigger a search, we want to take advantage of the default behavior of the web platform and use the form submit event that fires automatically when the user clicks the button or presses "Enter" while focused on the input or button. It's best to avoid reinventing the wheel and let the platform handle these common functionalities for us.
In addition to the text inputs we've been using so far, the web platform provides lots of additional form controls. They include:
Textareas
Radio buttons
Checkboxes
Selects
Ranges
Color pickers
We'll discuss these in some blogs later. If you find my blog meaningful or learned anything from it, a like would be highly appreciated. Thank you for reading!
Bye :)