Generate form from JSON schema in React

In this tutorial we will learn how to generate a form based on a JSON schema definition, on a React application

Introduction

In this tutorial we will learn how to generate a form based on a JSON schema definition, on a React application. We will use the react-jsonschema-form library, which will provide a high level component that allows use to generate the form in a very easy way.

This library can be installed with NPMN by sending the following command:

npm install react-jsonschema-form

The documentation of the library can be found here. If you want to play around with the capabilities of the library without jumping right away to the code, you can check here a live playground that allows to provide a JSON schema and check the rendered UI.

For simplicity, I’m writing the code from the examples below in a single file. Naturally, in a real application scenario, we should organize the application in reusable components and use an adequate folder structure.

I’ve setup the testing environment using Create React App, which is a great way to get started if you don’t have experience setting up a complex toolchain.

A basic example

We will start by the imports. We will import React and ReactDom, so we can write our React application. We will also import the Form component from the previously installed module.

import React from 'react';
import ReactDOM from 'react-dom';
import Form from 'react-jsonschema-form';

Followed by that, we will define a string with a very simple JSON schema. It will represent an object with 3 required properties:

  • Name: a string;
  • Age: an integer between 10 and 100;
  • Married: a Boolean, defaulting to the value false.

Note that our form component actually receives a JavaScript object representing this schema, so we will parse this string to an object before using it. You can check a tutorial on how to parse JSON with JavaScript here.

const schema = `{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer",
      "minimum": 10,
      "maximum": 100
    },
    "married": {
      "type": "boolean",
	  "default": false
    }
  },
  "required": [
    "name",
    "age",
    "married"
  ]
}`;

const schemaAsObject = JSON.parse(schema);

For simplicity, we will render the Form component directly without any wrapper component. This component has a prop called schema that basically should receive the JSON schema to which we want to render the form. We will pass our parsed JSON schema.

This component will be wrapped in a call to the render method of the ReactDOM object, so our form gets rendered. I’m assuming the HTML file that will contain our app has an element called “root“.

ReactDOM.render(
    <Form schema={schemaAsObject} />,
    document.getElementById('root')
);

The complete code can be seen below.

import React from 'react';
import ReactDOM from 'react-dom';
import Form from 'react-jsonschema-form';

const schema = `{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer",
      "minimum": 10,
      "maximum": 100
    },
    "married": {
      "type": "boolean",
	  "default": false
    }
  },
  "required": [
    "name",
    "age",
    "married"
  ]
}`;

const schemaAsObject = JSON.parse(schema);

ReactDOM.render(
    <Form schema={schemaAsObject} />,
    document.getElementById('root')
);

After compiling and serving our application, you should get an output similar to figure 1. As can be seen, the rendered form contains 3 controls, matching the properties we had in our JSON schema.

Application showing the rendered form.
Figure 1 – Application showing the rendered form.

If we try to submit the form without filling the required fields, we will get an error, like shown in figure 2 (my browser is in Portuguese, but the error indicates that the field should be filled).

Error in the form.
Figure 2 – Error in the form.

Note that, since we did not add any functionality to our form, even if we fill it correctly and we click the submit button, nothing will happen.

Wiring callbacks

Like we saw in the previous section, we could render the form but it was not very useful since nothing happens when we submit it. Naturally, we want to be able to capture the form submission event and get the data that the user filled.

To get more information about the form callbacks, please check here. As can be seen, there is a property of the Form component called onSubmit. So, we should pass to it a callback function.

Whenever the user submits the form, this callback will be called and an object with some properties will be passed as input.

On this object, the property we are interested on is called formData and it corresponds to an object where the properties are the names of the fields from the JSON schema and the values are what the user filled in the form.

So, we will define a function that we will use as this callback. It will simply access the form data and print it to the browser console.

function onFormSubmit (event) {
    console.log("---Form submitted---");
    console.log(event.formData);
}

For illustration purposes, we will also make use of the onChange prop of the Form component. If we pass a callback, it will be called every time the form changes.

When invoked, the callback function will receive also as input an object, like on the onSubmit case, where the fields filled so far by the user are going to be on a property called formData.

We will define another callback function to pass as this prop.

function onFormChange (event) {
    console.log("---Form changed---");
    console.log(event.formData);
}

The complete code can be seen below. Note that these two additional callbacks we defined are being passed as props of the Form component. Apart from that, the code is similar to the previous section.

import React from 'react';
import ReactDOM from 'react-dom';
import Form from 'react-jsonschema-form';

const schema = `{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer",
      "minimum": 10,
      "maximum": 100
    },
    "married": {
      "type": "boolean",
	  "default": false
    }
  },
  "required": [
    "name",
    "age",
    "married"
  ]
}`;

const schemaAsObject = JSON.parse(schema);

function onFormSubmit (event) {
    console.log("---Form submitted---");
    console.log(event.formData);
}

function onFormChange (event) {
    console.log("---Form changed---");
    console.log(event.formData);
}

ReactDOM.render(
    <Form 
      schema={schemaAsObject} 
      onSubmit={onFormSubmit}
      onChange={onFormChange}
    />,
    document.getElementById('root')
);

After compiling and serving the app, open the browser console, so you can see the output of the callback functions. As you are filling the form, the current state should get printed to the console. When you do the final submission, the corresponding callback should also get invoked, like shown on figure 3.

Output of the application, showing the callback prints in the browser console.
Figure 3 – Output of the application, showing the callback prints in the browser console.

This is a very simple example where we are printing the results of the form submission to the browser console. Naturally, in a real case application, this information would most likely be sent to the backend to be stored and the user would be redirected to other page, for example.

Improving the look of the form

Besides making the form work, we also want it to look good. Although we have seen in the previous sections that the form works, it doesn’t look very good. Fortunately, by default, the library renders form fields and widgets using the Bootstrap 3 semantics [1]. So, if we import the Bootstrap stylesheet, we will get a much better looking form.

To install Bootstrap with NPM, we just need to send the following command (make sure your command line directory is at your app’s folder):

npm install bootstrap@3

To import the CSS file, we can add the following line after our previous imports, in our app:

import 'bootstrap/dist/css/bootstrap.min.css';

Additionally, we will wrap the Form component in a div, so we can control the width of the form and center it. Otherwise, the Form will grow to occupy all the available space, which doesn’t look very good in bigger screens.

On that div, we will use Bootstrap’s grid system to center it and make it occupy 6 columns in medium and large devices.

<div className="col-md-offset-3 col-md-6">
      <Form 
        schema={schemaAsObject} 
        onSubmit={onFormSubmit}
        onChange={onFormChange}
      />
</div>

The complete code can be seen below.

import React from 'react';
import ReactDOM from 'react-dom';
import Form from 'react-jsonschema-form';
import 'bootstrap/dist/css/bootstrap.min.css';

const schema = `{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer",
      "minimum": 10,
      "maximum": 100
    },
    "married": {
      "type": "boolean",
	  "default": false
    }
  },
  "required": [
    "name",
    "age",
    "married"
  ]
}`;

const schemaAsObject = JSON.parse(schema);


function onFormSubmit (event) {
    console.log("---Form submitted---");
    console.log(event.formData);
}

function onFormChange (event) {
    console.log("---Form changed---");
    console.log(event.formData);
}

ReactDOM.render(
    <div className="col-md-offset-3 col-md-6">
      <Form 
        schema={schemaAsObject} 
        onSubmit={onFormSubmit}
        onChange={onFormChange}
      />
    </div>,
    document.getElementById('root')
);

Like before, compile your app and serve it. All the previous functionalities should keep working, since we did not change the corresponding code. The form should now have a better look, like on figure 4.

Note that I’ve reduced the size of my browser so the content fits the image well, but it should be responsive to width changes accordingly to what we have defined on our wrapping div.

Form styled with Bootstrap.
Figure 4 – Form styled with Bootstrap.

Testing conditions

References

[1] https://react-jsonschema-form.readthedocs.io/en/latest/usage/themes/

Leave a Reply

%d bloggers like this: