Single-Handedly Handling React Forms
Tags: react, beginners, tutorial, webdev
Published: 2022-11-17 16:37:57

Like most other things it offers, React provides a streamlined way to implement forms. Not only does the declarative syntax of JSX make building forms easier, but by leveraging state in combination with form elements, React allows us to control our forms' inputs. This enables easy collection of input data as well as other useful wizardry like input validation.

However, in learning how to make controlled forms with React, I found there's a sizable leap from the basics of setting up form inputs, each connected to its own independent state variable and each with a separate handler function, to the much more elegant and slick technique of using a single state variable and one handler function to manage an entire form. In this post, I'll walk through how to set up that trick.

_**Note:** This post assumes familiarity with the concept of controlled inputs and how to build a basic form in React._

For a very small form with just a couple of inputs, having a separate state variable and change handler function for each input isn't that much trouble. The more inputs you have, though, the more unwieldy your code becomes—and fast. If each input in our form component is set up like this:

```React
const [formNameInput, setFormNameInput] = useState("")
// ...
const handleNameInput = (e) => {
  setFormNameInput(e.target.value)
}
// ...
return (
  <form onSubmit={handleSubmit}>
    <input 
      type="text"
      value={formNameInput}
      onChange={handleNameInput}
    />
    // ...
  </form>
)
```
...We'll accumulate a large collection of state variables and state setter functions. We could cut some lines of code by invoking each input's respective setter function in-line inside the `onChange`, rather than writing individual handler functions, but even so the code for managing our form data will rapidly get out of hand. We can avoid this entire headache by using a single state variable to hold all our form data and one handler function that can tell which input it's being called from, and can accordingly update the right part of the form data in state.

### Objectify Your Data
The key to this (pun intended) is to store all our form data in an object, rather than in separate pieces. Let's say our form has three inputs: name, description, and number. We can initialize one state variable and keep all three pieces of data there inside an object:

```React
const [formData, setFormData] = useState({
  name: "",
  description: "",
  number: 0
})
```
When we invoke `useState`, we set its initial value to an object with keys that match our form's inputs (more on that later), and assign each key a default value (empty strings for name and description, and 0 for number).

As an aside, we could save this object to a separate variable, and use that to set our initial state:

```React
const initialFormState = {
  name: "",
  description: "",
  number: 0
}

const [formData, setFormData] = useState(initialFormState)
```
This isn't necessary, but it might look a little neater. It also comes in handy for resetting the form later.

###Input Names are Valuable
The second key part (strained pun still intended) of our setup is giving each of our form inputs a `name` attribute **that exactly matches the corresponding key** in the `formData` object:

```React
return (
  <form onSubmit={handleSubmit}>
    <input 
      type="text"
      value={formData.name}
      name="name"
      onChange={handleInput}
    />
    <input 
      type="textarea"
      value={formData.description}
      name="description"
      onChange={handleInput}
    />
    <input 
      type="number"
      value={formData.number}
      name="number"
      onChange={handleInput}
    />
    <input type="submit" />
  </form>
)
```
_This is essential!_ Each input element **must** have a `name` attribute with a value **exactly the same** as one of the keys in the `formData` object. These matching `name` attributes are how our single handler function will be able to know which key/value pair to update in `formData`.

Note also that we're now setting the `value` attribute of each input with a reference to the corresponding key in the `formData` object.

###Handling It All
With our `formData` object in a single state variable and our controlled inputs given matching `name` attributes, we can finally write our handler function:

```React
const handleInput = (e) => {
  const nameInput = e.target.name
  const valueInput = e.target.value

  setFormData({
    ...formData,
    [nameInput]: valueInput
  })
}
```
Let's break down what's going on here. First of all, notice we're still passing in the event object from whichever `onChange` invokes the function. Then we pull two pieces of information out of that event object, and assign them to variables: the `name` attribute of the input element from which `handleInput` was invoked, and the value the user has just entered into that input, which triggered the `onChange`.

Next, we invoke our state setter function `setFormData` so we can update state with the newly entered information. Since our state variable `formData` contains an object, we have to replace the whole object when we use the setter function to update it. Thus, we use the spread operator `...` to copy the entire current contents of `formData`, then specify which key/value pair to change in that copy using our variables `nameInput` and `valueInput`.

Note the square brackets around `nameInput`. This is just the syntax required to interpolate a variable for the name of an object's key in this case. This detail tripped me up when I first learned to write this kind of handler function.

Because we're pulling the `name` attribute from the input element, and because we made sure that `name` exactly matches a key in the `formData` object, our handler function can use the `name` attribute to specify which key/value pair to update with the state setter function. The other key/value pairs will remain unchanged—so whichever input the user interacts with, only that input's corresponding information in `formData` will change. Our one function can handle every input element in our form, no matter how many we add.

Finally, a further benefit of managing forms this way is that all your form information will already be packaged in a nice, neat object, ready to be taken in and passed on by your submit handler function. This obviates the work of building such an object from the data in separate state variables for each input element.

###Smoothing Things Out
We can refactor our single handler function to make it more compact. For one thing we could use a little object destructuring to simplify the variable assignment part:

```React
const handleInput = (e) => {
  const {name, value} = e.target

  setFormData({
    ...formData,
    [name]: value
  })
}
```
Here we're just taking `e.target.name` and `e.target.value` and assigning them to variables _also_ called `name` and `value`, then using those in the state setter function as before.

However, we can still do one better, and eliminate the variable assignment entirely:

```React
const handleInput = (e) => {
  setFormData({
    ...formData,
    [e.target.name]: e.target.value
  })
}
```
Turns out we don't even need those helper variables! Our handler function can just invoke the state setter function and directly use the `name` attribute and input value from the event object.

---
React makes forms pretty easy, but there's definitely a pronounced learning curve to go from the basic idea of connecting each input element to its own state to managing a whole form with one piece of state and one handler function, no matter how many inputs it has. However, not only is it worth doing so simply to make your code less cumbersome, but ultimately this approach is all but necessary to deal with forms with more than a tiny number of inputs.

Hopefully walking through each step of how to set up a React form like this helps you make that level-up.