If you’ve been building applications that accept user input via forms, you might have come across the need to make some form fields dynamic. This could be for various reasons, for instance, to add multiple members as part of a team during creation. If you’ve never been in this situation, this post might also be helpful for you at some point.
Prerequisites:
- Working knowledge of React Hooks
- npm installed in machine
We’ll be illustrating dynamic form fields in React using a simple form that has two input fields. One for the first name and another for the last name, these would form the user data. These two fields would be dynamically duplicated to create more fields that are unique and can accept new user data.
Getting Started
To quickly get started, we would be using the Create React App package, but the steps we would go over can be seamlessly integrated into your existing application.
Follow the steps below to create a new React app, navigate into the app directory and start it up in development mode.
npx create-react-app my-app
cd my-app
npm start
Open http://localhost:3000 to view it in the browser.
For some styling, we would install Bootstrap. This step is optional and only included here to give the form some good user interface. Knowledge of Bootstrap is not required.
npm i bootstrap
Deep Dive
Open the project in your favourite text editor. Navigate to App.js
and replace the content with the code snippet below:
import React, { useState, Fragment } from "react";
import "bootstrap/dist/css/bootstrap.css";
const App = () => {
return (
<>
<h1>Dynamic Form Fields in React</h1>
</>
)
}
export default App;
This gives us a basic template on which to build our form component.
Using React Hooks, initialise the form input fields like so:
...
const App = () => {
const [inputFields, setInputFields] = useState([
{ firstName: '', lastName: '' }
]);
...
}
...
In the above snippet, inputFields
refers to the form fields, while setInputFields
is the used to set the value for these form fields
Proceed to setup the form by updating the App
component as shown below:
const App = () => {
...
const handleSubmit = e => {
e.preventDefault();
console.log("inputFields", inputFields);
};
return (
<>
<h1>Dynamic Form Fields in React</h1>
<form onSubmit={handleSubmit}>
<div className="form-row">
{inputFields.map((inputField, index) => (
<Fragment key={`${inputField}~${index}`}>
<div className="form-group col-sm-6">
<label htmlFor="firstName">First Name</label>
<input
type="text"
className="form-control"
id="firstName"
name="firstName"
value={inputField.firstName}
/>
</div>
<div className="form-group col-sm-4">
<label htmlFor="lastName">Last Name</label>
<input
type="text"
className="form-control"
id="lastName"
name="lastName"
value={inputField.lastName}
/>
</div>
<div className="form-group col-sm-2">
<button
className="btn btn-link"
type="button"
>
-
</button>
<button
className="btn btn-link"
type="button"
>
+
</button>
</div>
</Fragment>
))}
</div>
<div className="submit-button">
<button
className="btn btn-primary mr-2"
type="submit"
onSubmit={handleSubmit}
>
Save
</button>
</div>
</form>
</>
)
}
...
If that looks like a lot of code thrown right at you, don’t worry, we’ll go over it. We have the function handleSubmit
to log the value returned when the form is submitted. Here we are simply logging to the console, but you could use the data here for whatever you need depending on your specific use case. The JavaScript map
function is used to create an array of the form fields. Fragment
from React allows us to group a list of children without adding a new node to the DOM.
Adding Functionality to the Form
Handling Changes
At this point, the basic UI is done that means we are getting closer to our goal, but there is no functionality yet. Roll up your sleeves, let’s get to work on the functionality!
Update the input fields to include a change handler to cater for user input action. See the change below:
...
<input
...
onChange={event => handleInputChange(index, event)}
/>
...
The handleInputChange
function does not exist. Let’s create it in the App
component right after handleSubmit
. See below:
...
const handleInputChange = (index, event) => {
const values = [...inputFields];
if (event.target.name === "firstName") {
values[index].firstName = event.target.value;
} else {
values[index].lastName = event.target.value;
}
setInputFields(values);
};
...
In the above code snippet, we spread inputFields
and do a simple check for the input field based on the name
attribute of that field. Then we supply the value for the given index. The index is derived from the map
function we used previously.
Adding and Removing Form Fields
Now comes the interesting part. Right after the handleInputChange
, add the following code snippet to handling adding and removing form fields:
...
const handleAddFields = () => {
const values = [...inputFields];
values.push({ firstName: '', lastName: '' });
setInputFields(values);
};
const handleRemoveFields = index => {
const values = [...inputFields];
values.splice(index, 1);
setInputFields(values);
};
...
To make use of these two new functions, update the -
and +
buttons to include click handlers to add and remove form fields.
...
<div className="form-group col-sm-2">
<button
className="btn btn-link"
type="button"
onClick={() => handleRemoveFields(index)}
>
-
</button>
<button
className="btn btn-link"
type="button"
onClick={() => handleAddFields()}
>
+
</button>
</div>
...
Aside: To see a preview of the form input values, add the following code snippet before the closing form tag </form>:
...
<br/>
<pre>
{JSON.stringify(inputFields, null, 2)}
</pre>
...
There you go! To add more form fields, click on +
, to remove click on -
. You can implement this functionality in your existing applications or new ones.
Preview:
Free free to share this with your team!
Originally published at: https://codeisbae.com/dynamic-form-fields-in-react/
Top comments (10)
Thanks for sharing!
I like this trick to mantain the original values untouched:
const values = [...inputFields];
.It's better to create a copy...even more when the original values are used as component state.
I'm glad to know the article was helpful to you.
Thanks for your feedback.
I like this article however I think you could use the useReducer hook especially as the form starts to grow with the addition of error messages.
Thanks, Benny. That sounds like an approach to give a try!
Nice article! Helped when I needed it
Nice article! It's helped me with the React side of a MERN application I'm trying to write.
Hey Duane, sorry for the late reply.
I'm not sure if you have resolved this yet. But, if not, maybe you could share some code snippet to give more context on the issue?
Great discussion. Thumbs up for thoroughly explaining everything while keeping an eye on the goal. Simple and straight to the point.
its a great article, how can i read the repeater control values in the parent class. Can you pls help with a sample ? thanks in advance.