Large Dynamically Generated Forms in React — Part 1

In this series, we will explore creating and optimizing dynamically generated forms.

Sometimes when a form is so large it is easier to manage it through data by dynamically generating it instead of manually coding the entire form. This has the benefits of being much faster to update when the client is looking to change a form and is reusable between projects.

In part 1, we will work through constructing the form.


We are going to create an app that allows the user to write a review of a band’s entire discography. For each of the band’s albums, we are going to ask the users a set of questions. For this example, we are going to use Muse (one of my all-time favorite bands).

Old Muse is better than new Muse

Data:

Muse have a total of 8 albums and I have created 8 questions to use to evaluate each album. Each album and field has its own id. Additionally, fields have an associated type which is used to generate the actual input.

Muse Data

Form:

Now that we have our data setup, we can move forward to create our form. We will start with some basic form markup:

<form style={styles.form} onSubmit={this.handleSubmit}>
<h1 style={styles.formTitle}>Dynamic Form</h1>
{/* Albums */}
<button type="submit">Submit</button>
</form>

For each album let’s create a title and a grouping of inputs:

{albums.map((album) => {
const { albumId, albumName } = album
return (
<div key={albumId} style={styles.albumWrapper}>
<div style={styles.titleWrapper}>
<h3 style={styles.title}>{albumName}</h3>
</div>
<div style={styles.body}>
{/* Fields */}
</div>
</div>
)
})}

If the map function is new to you read this.

Now for simplicity lets imagine all of our inputs were simple text inputs. We will now create a nice form row with a label and an input for each field:

{fields.map((field) => {
const { fieldId, fieldName } = field
const stateKey = `${albumId}_${fieldId}`
return (
<div style={styles.formRow} key={fieldId}>
<label
htmlFor={fieldId}
style={styles.rowLabel}
>
{fieldName}
</label>
<input
type="text"
id={fieldId}
style={styles.textInput}
onChange={(event) =>
this.setState({
[stateKey]: event.target.value
})
}
value={this.state[stateKey]}
/>
</div>
)
}

Here is where we start to run into some of the difficulties of managing a dynamically generated form. There are a number of different ways you can save the value into state. Some people prefer using the inputs event.target.name as the state key, you could also use a combination of array indexes or like I am doing a combination of unique keys. For this example, I use a combination of albumId and fieldId and set it to state using a computed property name.

const stateKey = `${albumId}_${fieldId}`
this.setState({ [stateKey]: e.target.value })

It’s also worth noting that I am setting these inputs up as a Controlled Component. With a controlled component you will need to set up an initial state for the inputs otherwise the form will error about switching from uncontrolled to controlled. However, it is very possible that you don’t need them to be controlled. If that is the case feel free to remove the value={this.state[`${albumId}_${fieldId}`]} on the input.

With that, you really have all of the tools to generate large forms. If you would like to see the full code with more types of inputs, check out the DynamicForm folder in the Github repo.

Full Dynamic Form Example

What you may notice when playing around with these large forms is that they can be a bit laggy while typing. That is because every time you type, the forms state is updated and thus the entire form is re-rendered. In this example, you might not notice any lag because our layout and inputs are so simple. In more complex code you could have more complex layout code such as putting each section in an accordion to make the page more manageable or more complex inputs like searchable dropdowns. As the form as a whole grows in complexity the more your browsers have to work to re-render on each keystroke.

In part 2, we will look at ways to optimize our dynamic form component using PureComponents, Context and the new React Profiler.


Source code for this entire series can be downloaded here.