Dynamically adding new fields to a form with React.js
I was recently working on a project where I had to conditionally add fields to a form and allow users to add more fields on the fly as they need them. At first, it appeared to be difficult and like the lazy developer I am, I googled to see if anyone had done something similar but I couldn’t find any so I set out to build one from scratch. What follows is my naive attempt at solving the problem just described. This is not a perfect solution and I would love to get some feedback on how to make it better.
The first thing I did was break the problem down into smaller pieces so I could think about it better. I noticed that the form needed to do a few things.
- Create a new form line.
- Create new fields and add the fields to the new line created.
- Add the new line to the original form
- Manage the state of the line items from the form.
Outlining the steps that way made it easier to reason about the code I needed to write.
So I went ahead and created the components to represent the steps outlined above. First I created the fields as shown in the code snippet below.
The next thing I needed to do was figure out a way to keep track of all such components created so that our form will be populated with these. To solve that problem I created another component that will act as the parent component, keeping track of how many line elements are created and rendering them as they are created.
The component starts out with one FormLine. When the user clicks on the add item button, a new FormLine component is added to the list of components and the parent component is re-rendered, showing the newly added component.
Now that we have solved the first three problems, the last problem and perhaps the most interesting is how to manage the state of the line items from the parent component. The first approach I used was to send the state of the whole line item component up to the parent component when the user blurs out of the last input field on the line item. However, I soon found out that the approach doesn’t scale well, for example, when the user types in both fields and blurs out, but then come back to the first input component and makes a correction, we lose that information.
To fix that problem I decided that pushing the state of the line component to the parent component on the blur of each field would be the best approach here. To achieve this, we would need to maintain a list of items that let the component add new items to the list and modify an item in the list. To do this, I needed to add an index field to the state of each line item so I can easily find and modify when needed. I modified the FormLine component to take the function that forwards its state to the parent component as a prop.
In the parent FormLineItems component, I wrote the pushItem function and pass it to the component as a prop. The component now looks like this:
Now when you type in text into the fields and hit on add item, you should see the current state of the component displayed above the text fields.
You can find the code for this article at https://gist.github.com/vdugeri/0a9f1972b80918b624e71b9aab35cb8e