How to Build a Dynamic Form Builder in React with JSON Data

The Form Builder app is the most complex we've built so far. It builds on concepts from earlier modules and reenforces the idea of building modular, component-driven UIs. We'll walk through the app build together to create a dynamic form powered from a set of JSON objects.

This lesson preview is part of the Beginner's Guide to Real World React course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Beginner's Guide to Real World React, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Beginner's Guide to Real World React
  • [00:00 - 00:37] 5. Building the Farm Builder Right, time to build something fun, the Farm Builder app. In this app we'll take a simple HTML form powered by React and allow it to load in a range of form fields dynamically from a JSON file. It's going to look like this when we're done. Before we start building anything in code, however, it's a great place to practice some of our thinking and react skills. As you get more experience, you can code up what you need to and look at refactoring this as you go. In fact, it can be really helpful to rapidly and roughly build something else and just get it working and then concern yourself with refining it once it does.

    [00:38 - 01:00] But for us at this stage, it's a super useful exercise to do a bit of planning beforehand to get a mental model and layout of what components we'll need to build and how they'll interact. So we're going to look for opportunities to break down components. Sticking with the finished demo that we've just looked at, if we were to code all this up in one big component, it would look something like this.

    [01:01 - 02:09] Ok, so this is already a really long component with a lot of repetition, are very, very close to having identical blocks of code, e.g. the label and input element combinations. Granted, some of this additional markup is due to the styling imposed by Bulma, but even without that, we'd still have a lot of repeated blocks of code. This has a few disadvantages. Readability becomes more difficult as the file or component grows in size or complexity, any universal changes, i.e. if we need to change the styling or structure or logic, need to be made in each and every separate block rather than in one place. More repetition usually introduces more places for errors to occur, and we also essentially lock in this form and make it concrete. For example, we can't reuse it anywhere, despite the fact that the mechanism of how it works, e.g. collecting data from the user and doing something with it , is going to be largely identical from form to form. So we'll create a visual map of a component. An improvement on this would be to look at the component we have and refactor it, thinking about how we can maximise reuse and minimise errors and lines of code. Thinking about the previous example and the previous demo of the app, we can create a visual mapping of how we might break down the single components into smaller parts. And that would look like this.

    [02:10 - 02:44] Looking at this visual mapping, you can see we have three main components and an additional file, formfields.json. In the app.js file, this is the usual project entry point that defines the starting place for our app to begin. In form.jsx, this will be a container component that pulls in our JSON data from the formfields.json file and uses it to populate a dynamic list of formfield input components. The formfield input.jsx file is a semi- presentational component that renders a HTML input with a label wrapped in a little bit of Bul ma markup. It manages its own state for the input's value.

    [02:45 - 03:21] And finally, we have the formfields.json. A simple JSON file that contains an array of objects, each representing a formfield with some information like a name, label text and input type. Onto the project setup. This part of the build should be starting to look familiar by now as we're going to be creating a new project with Create React app, removing some default files that we don't need and adding Bulma's CSS framework to the project. So, open up a terminal window and navigate to the parent folder that you want to create the new project in and next we'll type the Create React app command as follows. Yarn create react-app form-builder.

    [03:22 - 03:42] We'll let the command line finish installing the dependencies, waiting until you see the success message and Yarn commands to start and build the app. Let's give everything a quick check to make sure it's working before we take the coding hatchet out. So we'll type CD form-builder and yarn start.

    [03:43 - 03:54] Once it's built and ready, navigate to the HTTP localhost 3000 in a browser to check the project running as expected. Now it's time to do some initial cleanup.

    [03:55 - 04:29] Open the index.js file located in form-builder source directory and remove the default styles, the line import dot index dot CSS. It should be on line three of the index.js file. This will just remove a link to the default styles from the project that we don't need. After that, locate the source index dot CSS file and just delete it. Next we'll find the source app dot CSS file and open it up. Highlight all the contents and delete the existing styles. Save and close the file.

    [04:30 - 04:51] Finally, we'll open the main app dot js file located at source app dot js. Look at the following line round line two that imports a logo dot spg file and remove it. Now select everything in the return statement, everything between return, parenthesis and close parenthesis and replace it with the following so that the new return statement looks like this.

    [04:52 - 05:05] We'll add a h1 and add form-builder is ready to go. So the final stage in the setup is to once more bring Bulma on board to help style our app.

    [05:06 - 05:37] So we'll open up the file public index dot html and if you remember this is the template html file that the project uses to render the initial output of the app. Next we'll add the following line somewhere between the opening and closing head tags. So we're going to add a link which is the style sheet. You just link through to the Bulma CSS minified file. You can also edit the title of the page between the title tags too if you like.

    [05:38 - 05:57] We've given some thought to the files we want and how we're going to break the components down but where are we going to house everything. Let's start by creating two folders directly under our source folder. One that will hold our components and one for our JSON data. So we'll create a new folder components and another one called data.

    [05:58 - 06:30] When you start dealing with more complex projects and components that depend on one another it can be helpful to start with the child furthest down the tree and build upwards from there. That's how we're going to build our components for the form builder. We'll start with the JSON data and then we'll look at the form field input component and build upwards or backwards depending how you look at it from there. This is helpful because it allows us to decide what information a child component will need ahead of time and then make sure we pass that down from the parent component when we're building the parent out. Next creating form fields.json.

    [06:31 - 07:11] We want our HTML input elements to be rendered dynamically. This means they won 't know what to render ahead of time and we'll work this out at runtime deciding which part to the HTML to render and what type of field etc. This is good because it makes the component more flexible. We want to render a simple text box, no problem. We want to use the exact same component to render a date picker instead that has some helper text, fine and dandy. However at some point we do want to let our form field component know what we expect to do it and a good way to do that is with some good old-fashioned structured JSON data. Inside of the data folder create a new file called form fields.json and populate it with the following basic data.

    [07:12 - 08:11] [pause] Thinking about the sorts of properties that our HTML input elements can have and the other sort of information that we'd like to have displayed by our form field input structure we've arrived at this structure. Label the text we want displayed in the label element. Type the value for the type attribute on the input. This could be text, date, password, email, etc. Field name, the name or ID value for the fields. This will be used in the name attribute. Place all that. Any text we want to have displayed initially in the input box. Help text, a convenience for our users which will display a little help message underneath the input and required. A boolean value that forces users to enter some input. We can make some of these optional such as the help text and required values. Let's add a couple more fields to get a feel for how the rendered form fields will look in the end. You can see already that we've left a couple of properties out of the password and phone number objects and we'll discover how this affects the rendered output later on.

    [08:12 - 08:37] Creating the form field input component. The first link in the chain is the form field input component. We'll create a new file, form field input.json in the components folder. Let's fill it with the basic structure before we build it out from there.

    [08:38 - 08:48] Next up we need to define some props that the form field input component will receive. These will be pretty much the same as those in the JSON file we just defined.

    [08:49 - 09:06] We can add them into the component using the JavaScript destructuring syntax we've used previously. You can see they share the same name as the properties in the JSON file. This is useful as it means we can pass these directly from the JSON file into the component as we'll see a little bit later on.

    [09:07 - 09:24] The only additional prop we need is the handle field change. We'll use this as part of our input on change event to inform the parent component that our fields value has changed. Now we need to handle input changes. With our incoming props mapped out, we'll set up our state and handle any changes on the input element.

    [09:25 - 10:28] We're using the use state hook we imported at the top of the component to keep track of our input elements value. And with the handle on input change function we capture the synthetic event e that react kindly provides us. We use the value properly like this e.target. value to update our state value using the set value function returned from our hook. And the next part is an interesting one. However many of these components we add to the page they each only track their own state and values. But when we're thinking about form data we're thinking in terms of the overall form not individual values. So how do we capture all the data from the form if we have multiple inputs but they're all just tracking single values. Well one way is to have the parent component the one that renders all the individual input components keep all the input values together in one place in its own state. The way we're going to handle this is to have our child form field input component tell our parent component form that a value has changed using an event that form passes down to it.

    [10:29 - 10:38] The prop handle field change will be a function that accepts a field name and a field value. So here we check the function exists we don't want to call a function that hasn 't been passed in.

    [10:39 - 10:45] And if it does we call it passing in the synthetic events e.target.name and e. target.value values.

    [10:46 - 11:03] We're not going to worry too much about the mechanics of that function for now because ultimately as a child component we don't care too much about what happens in the parent. Now we need to define some JSX. The final part of the puzzle is to outline the HTML or JSX that will be rendered. Let's define that now and then walk through it.

    [11:04 - 11:54] There's nothing fancy in the HTML here but it does have a few extra bits and pieces of structure to make the styling work. You can read more about the generic controls and inputs and layouts and styling that directly in the Bulma CSS form documentation. The key points here are to look at where we're using our props. We're defining a standard HTML input element and assigning props to the various input elements attributes such as name, type and so on. The inputs value attribute is just using the value from state. This makes the input a controlled component from reactor point of view as it is managing the state of the input's value. Remember when we talked about some of the JSON objects not being necessary? Well you can see we've cared for that here. With placeholder, if that's empty or null then the input just won't display anything so that's fine.

    [11:55 - 12:47] With required we need to care to for null value. If we pass true or false that 's fine because required will exist and will have some sort of boolean value. However, if it is null or empty or undefined then this could cause issues. To care to for this we add a simple logical or short circuit the required pipe-pipe false and you become familiar with this as you look through react projects. It's a common expression to find that essentially says evaluate the first part of the expression and if it's false evaluate and return the second. So if required is null or undefined we set the attribute to false. We're doing a similar thing here for help text. We check to see if help text is available. If it is then we check the second part of the expression after the ampersand ampersand and return it. This happens to be a JSX expression which is also JavaScript but will get rendered out as a paragraph element containing our help text.

    [12:48 - 13:30] You can see that this is a really handy and neat looking way to dictate if a portion of JSX is shown or not without complex if statements or other complex mechanisms. So with that done here's what the complete form field input component will look like. Creating the form component. The parent for our form fields is our form component. Let's create that now by making a new file form.jsx in the component folder. In terms of complexity the form component acts as a middle amount of sorts. It collects and calls it input changes from child input components and then handles form submissions. Of course it also renders the necessary HTML form elements including a default submit button. Let's scaffold out the basic component first.

    [13:31 - 13:48] So nice and simple to start with. We're importing React and use state from React. Then we're bringing in our form field input component and defining a form component.

    [13:49 - 14:05] As part of the initial JSX has returned we're using a standard HTML form element with a react event on submit handled by the on form submit function we'll define in a moment . We added a default button element with some Bulma wrapping and classes for styling purposes.

    [14:06 - 14:18] The button doesn't wire up to any event handling but by default when a button element exists within a form element when it is clicked it triggers the forms on submit event. With our incoming props we just have two props to outline here.

    [14:19 - 14:47] Handle form submit and form fields. The former is a function that will be called when we handle our forms on submit event. Similar to the way in which the form field input handles its input value changes and then calls an event passed by the parent. The form component is going to do the same with handle form submit. The latter form fields will be an array of form field data objects that will loop through and use to render a separate form field input component. Now that we have our props defined we need to outline some variables and event handlers.

    [14:48 - 16:41] The form values variable uses the familiar use state hook and will use it to change in the child add or update any new form field values as they change. The handle form values change function is what we pass down component and its sole job is to take the updated value and add or amend it in our local form value state object. Similarly the on form submit function does a simple singular task . After preventing the form from causing a full page refresh using e.prevent default function it checks to save the props function handle form submit is available. If so it calls it passing up the current state of form values. In our case the parent component here will be app and we'll look at what we do with the form values it receives shortly and we build that component out. All that's left to do now is add a number of form field input components into the body of our form component . Leaving the majority of the current JSX we defined intact we're going to replace the commented section form fields here with the following code. Our form fields prop is an array of objects each containing a set of form field properties. We're using the map function on the array to loop through and return a new array full of JSX markup. In this case each item in the form fields array will return as a new form field input component. We're adding a key attribute which is vital when producing an output in a loop. This is what React uses to keep track of changes in repeated sections of JSX. Then we add in the handle form values change function to the attribute handle field change so that it's passed to the child component. However the next bit might look a little strange.

    [16:42 - 17:08] Take a look at the .dot.field details line. You might recognize the triple dot syntax as the object destructuring syntax built into newer versions of JavaScript. However we 're using it here as a shorthand instead of typing out each individual property on the form field input component. You remember from when we defined the component that it expects the following props. A label, a type, a field name, placeholder, help text, required and handle field change.

    [17:09 - 17:44] Well with the exception of handle field change all the other props are named identically to the object properties on one of our form field JSN objects. What this means is that by saying .dot.field details on the component we're really saying please copy all of the properties and values from the field details object which represents a single item in the form field array onto our form field input component. Let's quickly highlight this with a code example. Although the second version is more explicit in that we know exactly what's being passed into the form field input component the first version is much neater and serves the same purpose.

    [17:45 - 18:39] It would be much more fitting if it were part of a larger more complex component that we could refactor to make more readable. With that in place here's what the complete form component should look like. Editing app.js. With all the building boxing players the last thing to do is wire up everything into our starting point the app component. Open up the app. js component in the project route and we'll need to add some imports first. Notice that we're bringing in both the form component and the JSN data from the form fields.jsn file. Next inside of our app functional component we need to use the use state to set a place to house the collected form values when the HTML form is submitted. And finally we can add in the JSX.

    [18:40 - 19:16] First let's add in the structural elements infused with Bulma styles. Everything so far should look pretty straightforward. Bulma like most CSS frameworks dictates a lot of additional styles and additional markup to layout and style everything according to its own approach. Here we're adding a section, container, title and some columns which Bulma bases on the Flexbox CSS model. The box class will give our form a nice surrounding border and box shadow. Meanwhile on the other side the notification class will highlight and will eventually outputted form values with a background and some spacing.

    [19:17 - 20:39] The only other thing for now is that we have a clear results button that calls a set submitted values method to reset the value instead, effectively clearing out the results that will display in a moment. Speaking of which let's output some of the form values we can expect to receive when the form is submitted. The first code block uses the logical and shortcut to check if the submitted form values object instead has any values. We use JavaScript's object.values function for this job. You can read the MDN developer documentation on this for more information. If the object instead doesn't have any values we know it's an empty object so we check the other side of the expression. The right hand side happens to be a paragraph tag in form in the user that they need to submit the form to see some results. Next we define a standard un ordered list and do a similar statement as we've just done but this time we look through each key value pair in the submitted form values object using javascript object.entries. Again there's some great documentation on the MDN developer side. Each item in the loop gives us both a key and a value for each property in the object instead. For each key pair value we're returning and rendering a HTML list item element that contains this pairs value. For example if the current object property was name Rob Kendall then we'll just be grabbing Rob Kendall. Finally let's add the form component.

    [20:40 - 20:58] Simple right? We've already done all the hard work by defining and building out our components. Now we have the relaxing job of just adding in the component. The only remaining thing to do is to wire up the form component's props, namely the handle form submit event and the form fields.

    [20:59 - 21:34] You can see that we've passed along the fields array of my adjacent file into the form fields prop and that's that done. With the handle form submit prop we're just going to run an inline arrow function here that receives a values object containing the form field names and their values and we immediately call the set submitted form values to update his names instead. As soon as the value of submitted form values instead changes the component will render again and our notification area will be populated assuming of course a submitted form values contains any values. With that done here's what the complete app component should look like.

    [21:35 - 22:08] Running the project. So all that's left to do is run the project and marvel at our handy work. This might look like a fairly simple project on the surface but it's decept ively deep when you get into it. We've abstracted a lot of repeated code into reusable independent components that each manage their own state. We've employed some structured JSON data to dynamically dictate what types of form fields to render and what's more we've started to think and react to achieve all of this. Passing values and information down through props and backup via events and handler functions.

    [22:09 - 22:48] If you're not already running your project to check everything out then open up the terminal, locate the code folder and run the starting command yarn start. Enter some data into the form, check out the built-in HTML validation and play it on the required fields and try submitting the form. Notice how the notification display updates with whatever you enter in the form? Pretty cool right? Now for the real magic. The power of our dynamically generated form really shines through if we change any of the JSON data in the forms field.json file.

    [22:49 - 23:52] Let's do that now and check out the results. I'll add in a new field in here and change some of the details on the others and as soon as I hit save on the file watch the form in the local site update to reflect those changes. If I submit the form again notice how the correct data is being pulled together and displayed in the notification area. Now that's really cool and so powerful for two components and not that many lines of code. We've built something really useful here that offers a lot of flexibility and power in such a small package. There's lots of ways to extend it which could offer some challenges and homework that you could take an experiment with. For example you could look at adding dynamic validation to the form field components, adding different types of HTML form elements such as select text areas or sliders and you could add some moving parts to the form components such as notifications on submission, callback functions and introductory messaging.

    [23:53 - 24:14] [ No audio ]