Please Provide the Following Information

The resource you requested will be sent via email.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Please Provide the Following Information

The resource you requested will be sent via email.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Please Provide the Following Information

The resource you requested will be sent via email.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Please Provide the Following Information

The resource you requested will be sent via email.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Convert your JSON to Forms in React
Blogs
February 23, 2021

Convert your JSON to Forms in React

Introduction

Forms are an important part of any application. With React, building forms and managing them can be quite a mundane task. Each form needs to be built from scratch while handling validations and even taking care of the performance aspect. Managing all these things manually in each and every form becomes a challenge when the application grows quite big and you end up with hundreds of forms in your application. Fortunately, this process can be automated and can save a lot of time spent working on individual forms. One of the ways to achieve this is with the help of the JSON object.

In this blog, we’ll be building a form generator component that converts a custom, JSON schema into a form with custom field types and our own validation rules. There are already packages available like react-jsonschema-form (which has a huge footprint) and react-schema-form (which is lightweight but only supports material-ui), but the advantage of having our own component is the size, performance, and customization that we can add to it. We can pick our own JSON schema, set our own validation rules, and easily style it the way we want.

We’ll not be building everything from the ground up but we will be using react-final-form to build our form. It is a really lightweight form builder that is perfect for what we are looking to build. We just need to write a custom logic to convert our JSON to individual form elements.

Let’s first come up with a json schema.

const schema = { firstName: { element: 'text', label: 'First Name', placeholder: 'Enter first name', required: true, validation: { regex: '^[a-zA-Z0–9 ]+$', message: 'Only contain alphanumeric characters allowed' } } };

So, each field will be a key in our schema and the object will support element type, label, placeholder, required flag, and validation as the properties. This structure will be a bit different for radio, checkboxes, and dropdown.

... gender: { element: 'radio', label: 'Select one', options: [['yes', 'Yes'], ['no', 'No']], required: true }, city: { element: 'select', label: 'Select color', placeholder: 'Select a color', options: ['Red', 'Green', 'Blue'], required: true }, ...

For the simplicity of this article, we will only include text, radio, and select input types. Other field types can be supported in the similar fashion.

Now that we have our JSON schema ready, let's use react-final-form to generate fields and look at the syntax of react-final-form.

import { Form, Field } from 'react-final-form'; <Form onSubmit={onSubmit} render={({ handleSubmit }) => ( <form onSubmit={handleSubmit}> <div> <label>First Name</label> <Field name="firstName" component="input" type="text" placeholder="First Name" /> </div> <button type="submit">Submit</button> </form> )} />

It has a render method that takes in a function that returns our form. However, instead of our normal input tags, we use the ‘Field’ component from react-final-form. Hence, we need a separate ‘Field’ component for each of our input types.

We can create a new component for each of these field types, like below, that will simply return the Field component with the provided name and placeholder for the text input field.

const TextField = ({ name, placeholder }) => ( <Field name={name}> {({ input }) => ( <div> <input {...input} type="text" placeholder={placeholder} /> </div> )} </Field> )

For radio and select elements, we need options as well as those which we have defined in our JSON as array. Let’s convert it to a simple object.

const getSchemaFieldOptions = (schema, field) => schema[field].options.map(option => { let label = option; let value = option; if (Array.isArray(option)) { value = option[0]; label = option[1] || option[0]; } return { value, label }; }

With this function, our options array in JSON schema will be converted to an object.

[['yes', 'Yes'], ['no', 'Not Yes']] // converted to [ { label: 'Yes', value: 'yes', }, { label: 'Not Yes', value: 'no', } ]

This will enable us to work with the options much easier. We shall pass this to our RadioField component as props. Our RadioField will look something like this.

const RadioField = ({ name, fieldOptions }) => ( <Field name={name}> {({ input }) => ( <div> {fieldOptions.map(option => ( <React.Fragment key={option.value}> <input {...input} type="radio" id={option.value} value={option.value} checked={ input.value === option.value } onChange={e => input.onChange(e.target.value)} /> <label htmlFor={option.value}>{option.label}</label> </React.Fragment> ))} </div> )} </Field> )

SelectField can also be created using the same way we did for RadioField.

const SelectField = ({ name, placeholder, fieldOptions }) => ( <Field name={name}> {({ input }) => ( <div> <select {...input}> <option value=""> {placeholder || '--Select an option--'} </option> {fieldOptions.map(option => ( <option key={option.value} value={option.value} {option.label} </option> ))} </select> </div> )} </Field> )

Now that we have all our required functions, let’s put them together.

... const schemaFields = Object.keys(schema) || []; return ( <Form onSubmit={handleSubmit} render={({ handleSubmit }) => ( <form onSubmit={handleSubmit}> {schemaFields.map(field => ( <div key={field}> {schema[field].element === 'text' && <TextField name={field} placeholder={schema[field].placeholder} /> } {schema[field].element === 'radio' && <RadioField name={field} fieldOptions={getSchemaFieldOptions(schema, field)} /> } {schema[field].element === 'select' && <SelectField name={field} fieldOptions={getSchemaFieldOptions(schema, field)} placeholder={schema[field].placeholder} /> } </div> ))} </form> )} /> ); ...

We can clean this code up to make it a bit more readable.

We now have a basic version of our JSON to form converter working. With some basic styling, we have a form that looks like this.

Validation

Remember, we have a validation field and required flags in our JSON schema but we haven’t used them anywhere. React Final Form allows us to validate the form with the ‘validate’ prop. We need to pass in a function that will return the validation errors as an array of objects with field name and error message.

We will be using yup, which is a validation library and it will handle all the validation logics for us. Let’s look at how yup works.

import * as yup from 'yup'; let schema = yup.object().shape({ name: yup.string().required(), age: yup.number().required().positive().integer(), email: yup.string().email(), website: yup.string().url(), createdOn: yup.date().default(function () { return new Date(); }), }); try { schema.validateSync({ name: 'jimmy', age: '24', createdOn: '2014-09-23T19:25:25Z', }); } catch (err) { // handle validation errors }

We need a validation schema that contains all the rules for each of our fields. A simple function like below can create our validation schema from our JSON schema.

This will check for our ‘required’ flag and ‘validation’ regex in our JSON schema. You can go ahead and apply custom logic here based on additional validation rules in your JSON schema like ‘minLength’, ‘maxLength’, etc.

Now let’s create a function that will validate our form against the above validation schema.

const validate = (values, validationSchema) => { const errors = {} try { validationSchema.validateSync(values, { abortEarly: false }); } catch (err) { err.inner.forEach(e => { errors[e.path] = e.message; }); } return errors; }

The ‘abortEarly’ false flag will make sure all the fields are validated. If you don’t want that behavior, you can skip it.

Let’s plug the validate function in our Form component.

... const validationSchema = getValidationSchema(schema); return ( <Form validate={values => validate(values, validationSchema)} ... /> ); ...

The error message will then be available in the ‘meta’ prop inside the ‘Field’ component. We need to add it to all our custom Field components like below.

const InputField = ({ name, placeholder }) => { return ( <Field name={name}> {({ input, meta }) => ( <div> <input {...input} type="text" placeholder={placeholder} /> {meta.error && meta.touched && <div className="final-error">{meta.error}</div> } </div> )} </Field> ) }

If you leave all fields empty and hit submit, you should see error messages like below.

Pro Tip

Right now, if you try to see the re-rendering pattern of the form, you will notice that on every key press, the whole form is re-rendered. You can simply prevent this behavior and only re-render the field where input value has changed by adding subscription={{ submitting: true, pristine: true }} flag to the Form component.

... <Form subscription={{ submitting: true, pristine: true }} ... />

With a little bit of styling and bootstrap css, the form component is now ready and the final end product looks like this.

And there you have it! You have a fully functional component that’ll convert your JSON schema into forms in React. You can add your own validation rules and styling options to suit the need of your application.

Thank you for reading this blog. If you liked it, do checkout our other blogs and articles from Botsplash as well.

Introduction

Forms are an important part of any application. With React, building forms and managing them can be quite a mundane task. Each form needs to be built from scratch while handling validations and even taking care of the performance aspect. Managing all these things manually in each and every form becomes a challenge when the application grows quite big and you end up with hundreds of forms in your application. Fortunately, this process can be automated and can save a lot of time spent working on individual forms. One of the ways to achieve this is with the help of the JSON object.

In this blog, we’ll be building a form generator component that converts a custom, JSON schema into a form with custom field types and our own validation rules. There are already packages available like react-jsonschema-form (which has a huge footprint) and react-schema-form (which is lightweight but only supports material-ui), but the advantage of having our own component is the size, performance, and customization that we can add to it. We can pick our own JSON schema, set our own validation rules, and easily style it the way we want.

We’ll not be building everything from the ground up but we will be using react-final-form to build our form. It is a really lightweight form builder that is perfect for what we are looking to build. We just need to write a custom logic to convert our JSON to individual form elements.

Let’s first come up with a json schema.

const schema = { firstName: { element: 'text', label: 'First Name', placeholder: 'Enter first name', required: true, validation: { regex: '^[a-zA-Z0–9 ]+$', message: 'Only contain alphanumeric characters allowed' } } };

So, each field will be a key in our schema and the object will support element type, label, placeholder, required flag, and validation as the properties. This structure will be a bit different for radio, checkboxes, and dropdown.

... gender: { element: 'radio', label: 'Select one', options: [['yes', 'Yes'], ['no', 'No']], required: true }, city: { element: 'select', label: 'Select color', placeholder: 'Select a color', options: ['Red', 'Green', 'Blue'], required: true }, ...

For the simplicity of this article, we will only include text, radio, and select input types. Other field types can be supported in the similar fashion.

Now that we have our JSON schema ready, let's use react-final-form to generate fields and look at the syntax of react-final-form.

import { Form, Field } from 'react-final-form'; <Form onSubmit={onSubmit} render={({ handleSubmit }) => ( <form onSubmit={handleSubmit}> <div> <label>First Name</label> <Field name="firstName" component="input" type="text" placeholder="First Name" /> </div> <button type="submit">Submit</button> </form> )} />

It has a render method that takes in a function that returns our form. However, instead of our normal input tags, we use the ‘Field’ component from react-final-form. Hence, we need a separate ‘Field’ component for each of our input types.

We can create a new component for each of these field types, like below, that will simply return the Field component with the provided name and placeholder for the text input field.

const TextField = ({ name, placeholder }) => ( <Field name={name}> {({ input }) => ( <div> <input {...input} type="text" placeholder={placeholder} /> </div> )} </Field> )

For radio and select elements, we need options as well as those which we have defined in our JSON as array. Let’s convert it to a simple object.

const getSchemaFieldOptions = (schema, field) => schema[field].options.map(option => { let label = option; let value = option; if (Array.isArray(option)) { value = option[0]; label = option[1] || option[0]; } return { value, label }; }

With this function, our options array in JSON schema will be converted to an object.

[['yes', 'Yes'], ['no', 'Not Yes']] // converted to [ { label: 'Yes', value: 'yes', }, { label: 'Not Yes', value: 'no', } ]

This will enable us to work with the options much easier. We shall pass this to our RadioField component as props. Our RadioField will look something like this.

const RadioField = ({ name, fieldOptions }) => ( <Field name={name}> {({ input }) => ( <div> {fieldOptions.map(option => ( <React.Fragment key={option.value}> <input {...input} type="radio" id={option.value} value={option.value} checked={ input.value === option.value } onChange={e => input.onChange(e.target.value)} /> <label htmlFor={option.value}>{option.label}</label> </React.Fragment> ))} </div> )} </Field> )

SelectField can also be created using the same way we did for RadioField.

const SelectField = ({ name, placeholder, fieldOptions }) => ( <Field name={name}> {({ input }) => ( <div> <select {...input}> <option value=""> {placeholder || '--Select an option--'} </option> {fieldOptions.map(option => ( <option key={option.value} value={option.value} {option.label} </option> ))} </select> </div> )} </Field> )

Now that we have all our required functions, let’s put them together.

... const schemaFields = Object.keys(schema) || []; return ( <Form onSubmit={handleSubmit} render={({ handleSubmit }) => ( <form onSubmit={handleSubmit}> {schemaFields.map(field => ( <div key={field}> {schema[field].element === 'text' && <TextField name={field} placeholder={schema[field].placeholder} /> } {schema[field].element === 'radio' && <RadioField name={field} fieldOptions={getSchemaFieldOptions(schema, field)} /> } {schema[field].element === 'select' && <SelectField name={field} fieldOptions={getSchemaFieldOptions(schema, field)} placeholder={schema[field].placeholder} /> } </div> ))} </form> )} /> ); ...

We can clean this code up to make it a bit more readable.

We now have a basic version of our JSON to form converter working. With some basic styling, we have a form that looks like this.

Validation

Remember, we have a validation field and required flags in our JSON schema but we haven’t used them anywhere. React Final Form allows us to validate the form with the ‘validate’ prop. We need to pass in a function that will return the validation errors as an array of objects with field name and error message.

We will be using yup, which is a validation library and it will handle all the validation logics for us. Let’s look at how yup works.

import * as yup from 'yup'; let schema = yup.object().shape({ name: yup.string().required(), age: yup.number().required().positive().integer(), email: yup.string().email(), website: yup.string().url(), createdOn: yup.date().default(function () { return new Date(); }), }); try { schema.validateSync({ name: 'jimmy', age: '24', createdOn: '2014-09-23T19:25:25Z', }); } catch (err) { // handle validation errors }

We need a validation schema that contains all the rules for each of our fields. A simple function like below can create our validation schema from our JSON schema.

This will check for our ‘required’ flag and ‘validation’ regex in our JSON schema. You can go ahead and apply custom logic here based on additional validation rules in your JSON schema like ‘minLength’, ‘maxLength’, etc.

Now let’s create a function that will validate our form against the above validation schema.

const validate = (values, validationSchema) => { const errors = {} try { validationSchema.validateSync(values, { abortEarly: false }); } catch (err) { err.inner.forEach(e => { errors[e.path] = e.message; }); } return errors; }

The ‘abortEarly’ false flag will make sure all the fields are validated. If you don’t want that behavior, you can skip it.

Let’s plug the validate function in our Form component.

... const validationSchema = getValidationSchema(schema); return ( <Form validate={values => validate(values, validationSchema)} ... /> ); ...

The error message will then be available in the ‘meta’ prop inside the ‘Field’ component. We need to add it to all our custom Field components like below.

const InputField = ({ name, placeholder }) => { return ( <Field name={name}> {({ input, meta }) => ( <div> <input {...input} type="text" placeholder={placeholder} /> {meta.error && meta.touched && <div className="final-error">{meta.error}</div> } </div> )} </Field> ) }

If you leave all fields empty and hit submit, you should see error messages like below.

Pro Tip

Right now, if you try to see the re-rendering pattern of the form, you will notice that on every key press, the whole form is re-rendered. You can simply prevent this behavior and only re-render the field where input value has changed by adding subscription={{ submitting: true, pristine: true }} flag to the Form component.

... <Form subscription={{ submitting: true, pristine: true }} ... />

With a little bit of styling and bootstrap css, the form component is now ready and the final end product looks like this.

And there you have it! You have a fully functional component that’ll convert your JSON schema into forms in React. You can add your own validation rules and styling options to suit the need of your application.

Thank you for reading this blog. If you liked it, do checkout our other blogs and articles from Botsplash as well.

Subscribe to our newsletter... we promise no spam

Botsplash Logo
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.