Dynamic Form Inputs with React Controlled Forms

The goal of this blog post is to demonstrate how to create a React controlled form (i.e. form fields with value={someState} && onChange={handleChange}) which has a varying number of form inputs. Here is a GIF showcasing this behavior from a project I’m working on:

As you can see, a user is able to add form fields by clicking the “Add Wine” button, and remove specific form fields by clicking the minus button next to the field they wish to remove. It turns out this is a non-trivial feature to implement, and one which may have many different patterns. I will show you the pattern I used by walking through an example with a slightly less specific example than the one I GIFed above.

Let’s start with a basic create-react-app structure:

And our components, which will live in their own components folder inside src:

In order to run this we will need to run:

$npm i bootstrap
$npm i react-bootstrap

And after npm start we should see this page:

Now we need to get set up with our useState hook to hold onto the values that we’ll use for our form inputs. Right underneath the function declaration in DnyamicForm.js we can include the following code:

Now that we have our blueprint for what the formInputs state will look like, we can go ahead and create our renderFormFields method:

And our GIF (note I added e.target.id to the console.log):

Now let’s set up our handleChange method to actually change the formInput state:

This handleChange method preserves the values held at other indices in our formInputs state as well as any other inputs at the currently changing index. As long as your form has inputs with name=”nameMatchingFormInputNameInState” then you should be good to go.

At this point we need 3 more features: Add Row, Submit, and Delete Row. Let’s do Add Row:

And if we add an onClick={addRow} to our Add Row button we will see the following behavior:

For our removeRow let’s throw a little Font Awesome Icon into the mix.

We can import an appropriate icon from FontAwesome and throw it in a third column in our renderFormFields() method. Give it an id={key} and an onClick={removeRow} and we’ll be good to go.

And here’s the method:

A teensy bit of styling later and Voila:

This leaves us with just one last detail: submitting the form. You should probably already know how to do this if you’re looking up how to make controlled forms with varying numbers of input fields so I won’t bother.

And for those that just want easy copy pasta, here’s the completed DynamicForm.js as well as the App.css:

Enjoy :)