Jun 8, 2020 | 4 min read

Dynamic rendering in React

Have you ever wondered how CMS like Wordpress, Wix, etc works under the hood?

Look no further. We will be diving in to the fundamental parts of how a CMS is able to achieve its functionality by talking about how to dynamically render a React component with a JSON configuration while building a simple form component.

Getting Started

There are two main parts to rendering React components dynamically:

  • A custom renderer to handle the rendering of components, This needs to be as flexible as possible so that it can handle different use-cases or configurations.
  • The JSON configuration for the custom renderer.

Renderer

Create a file and call it Renderer.js. This will be used to render the JSON config, which you will do in the next section. Take a look at the Renderer component and what it does:

// Renderer.js import React, { Fragment } from "react"; const mapPropsToConfig = (config) => { const configWithProps = []; config.forEach((item) => { if (item.component) { const { component, ...props } = item; configWithProps.push({ ...props, Component: component, }); } }); return configWithProps; }; export const Renderer = ({ config }) => { if (!config) { throw new Error("You are calling Renderer with no config."); } const configWithProps = mapPropsToConfig(config); const renderComponents = (items) => { return items.map((item) => { const { Component, ...props } = item; return ( <Fragment key={props.name}> <Component {...props} /> </Fragment> ); }); }; return renderComponents(configWithProps); };

From the code above, the renderer takes in a single config prop, which is an array of objects. Each item comprises of:

  • name: A unique property that singly identifies the component.
  • component: The React component to be rendered.

Note: Any other properties in the object will be passed on as props into the component. The rest of the code is pretty much straightforward.

Next, create the config that needs to be passed down to the renderer Component.

Config

Before you can come up with a proper config, you need:

  1. A components map from your respective components.
  2. You can create them on the fly depending on your use case.

For the purpose of this guide, you will be building a simple Form Component that comprises of input fields for a full name, email address and a phone number.

Firstly, you need to create the input field component called InputField.js with the following content:

// InputField.js import React from "react"; const InputField = ({ label, ...props }) => ( <div> {label ? <label htmlFor={props.id}>{label}</label> : null} <input {...props} /> </div> ); export default InputField;

Finally, the form component config will contain the following:

// config.js export const config = [ { name: "fullName", id: "fullName", type: "text", component: InputField, value: "John Doe", label: "Full Name", }, { name: "email", id: "email", type: "text", component: InputField, value: "john.doe@pluralsight.com", label: "Email Address", }, { name: "phoneNumber", id: "phoneNumber", type: "tel", component: InputField, value: "12345678", label: "Phone Number", }, ];

Putting it all together

Now that you have created the Renderer and the config, you are now ready to render the Form Component and some interactivity.

Create Form.js with the following content:

//Form.js import { Renderer } from "./Renderer"; import { config } from "./config"; const Form = () => { return ( <form> <Renderer config={config} /> </form> ); };

The code above just renders a form fields that are defined in the config without actually doing anything. We will fix that in the next section.

Interactivity

In order to add some interactivity to the Form Component, you will need to modify the config.js to accept the config state and an onChange handler.

// config.js import InputField from "./InputField.js"; export const getConfig = ({ state, onChange }) => { return [ { name: "fullName", id: "fullName", type: "text", label: "Full Name", component: InputField, value: state["fullName"], onChange: onChange("fullName"), }, { name: "email", id: "email", type: "text", component: InputField, label: "Email Address", value: state["email"], onChange: onChange("email"), }, { name: "phoneNumber", id: "email", type: "tel", component: InputField, label: "Phone Number", value: state["phoneNumber"], onChange: onChange("phoneNumber"), }, ]; };

You will also have to update the Form Component to use the updated config file:

// Form.js import React, { useState, useMemo } from "react"; import { Renderer } from "./Renderer"; import { getConfig } from "./config"; export const Form = () => { const [state, setState] = useState({ fullName: "", email: "", phoneNumber: "", }); const handleSubmit = (e) => { e.preventDefault(); console.log("state", state); }; const handleOnChange = (field) => (event) => { const { value } = event.target; setState((prevState) => ({ ...prevState, [field]: value, })); }; const config = useMemo(() => { return getConfig({ state, onChange: handleOnChange, }); }, [state]); return ( <form onSubmit={handleSubmit}> <Renderer config={config} /> <button>Submit</button> </form> ); };

With an updated Form Component file with some react state and functions, you have successfully created dynamic Form with Interactivity. Here is the link to the codesandbox for the App

For more complex use-case, you can check out form-fields-react for more.

Conclusion

In this guide, we have looked at how we can render React components dynamically using a Custom Renderer and a JSON config by building a simple Form Component.

Here are some useful links to learn more about dynamic rendering of react components