Case StudyFront End

Unleashing the Power of JSON Renderer: Crafting Dynamic and Data-Driven User Interfaces in React Applications

Irshadi Bagasputro

30 July 2023 · 7 min read

Param Config - A Dynamic JSON Renderer

Parameter Configurator

Param Config Introduction

In the fascinating world of React applications, engineers often encounter a need to dynamically render user interfaces based on data received in JSON format. This is where the idea of a React JSON Renderer comes into play. At its core, this concept revolves around the transformation of JSON data into visually engaging React components or other UI elements.

To use our product effectively, users often need to configure numerous settings, a task that involves creating multiple inputs and distributing them across various sections—a repetitive and time-consuming process that can hinder development progress.

This time I will share the process of making param config…

Param Config Flow

But what exactly is Param Config? From a frontend perspective, it comprises a set of instructions that dictate which inputs to render, how to render them, and what data they represent. On the backend, it defines how the frontend renders data, the rendering methods employed, and the data sources used for these inputs.

To grasp the concept visually, take a look at the illustration below:

param-config-flow.png

param-config-flow.png

Basic Examples

Let's delve into the example below, where we aim to render two distinct input fields: a dropdown input and a text input.

param-config-basic.png

param-config-basic.png

The magic lies in utilizing param config to effortlessly achieve this goal. By constructing an object with two essential keys—namely field and name—we pave the way for generating the desired input configuration.

1

const response = {

2

field: {

3

description: "Select column to rename",

4

input_type: "dropdown",

5

options: {

6

display: "name",

7

value: "value",

8

data: [

9

{

10

name: "Permalink",

11

value: "permalink"

12

},

13

{

14

name: "Company",

15

value: "company"

16

}

17

]

18

}

19

},

20

name: {

21

description: "New name for the column",

22

required: true

23

}

24

};

This return value is thoughtfully generated by the backend, simplifying the process for seamless frontend rendering.

With param config in place, the frontend gains the power to dynamically generate inputs based on specific instructions received from the backend, streamlining the development process and offering a flexible and user-friendly interface.

Param Conifg Feature

Our Param Config is designed to offer flexibility while maintaining clear boundaries within the feature scope. Some properties include advanced features that support various functions like Layout Grid, Conditional Rendering, and Dynamic Options.

These advanced properties open up possibilities. The Layout Grid allows easy arrangement of components for visually stunning displays. Conditional Rendering adds an interesting touch, showing or hiding components based on specific conditions, enhancing the user interface. With Dynamic Options we have the flexibility to either provide a hard-coded data, or utilize Conditional Data Fetching, we can retrieve data only when needed, optimizing performance and efficiency in every interaction.

Layout Grid

Layout Grid's: effortlessly group multiple inputs in a sleek horizontal stack by adding the special group_id identifier. It brings style and organization to your interface, creating a delightful user experience. With Layout Grid, several inputs can be grouped into a horizontal stack. To achieve this you need to append an identifier key group_id.

param-config-layout-grid.png

param-config-layout-grid.png

Example above groups “Select Operator” and “Value” with one group_id to achieve horizontal grid.

1

const response = {

2

select_field: {

3

title: "Select Field",

4

input_type: "dropdown",

5

options: { ...options }

6

},

7

select_operator: {

8

title: "Select Operator",

9

input_type: "dropdown",

10

group_id: "60416fe",

11

options: {

12

display: "name",

13

value: "value",

14

data: [

15

{

16

name: "Greater Than",

17

value: "gt"

18

},

19

{

20

name: "Lesser Than",

21

value: "lt"

22

}

23

]

24

}

25

},

26

value: {

27

title: "Value",

28

group_id: "60416fe"

29

}

30

};

Conditional Rendering

Conditional Rendering brings the power of invisibility, hiding multiple inputs from users until specific render conditions are met. When the given criteria aligns, the front-end conjures those inputs into view.

param-config-conditional-render-2.gif

param-config-conditional-render-2.gif

1

const response = {

2

ssh_auth: {

3

title: "SSH authentication",

4

description: "Method to authenticate the SSH tunneling",

5

input_type: "dropdown",

6

options: {

7

display: "name",

8

value: "value",

9

data: [

10

{

11

name: "Password",

12

value: "password"

13

},

14

{

15

name: "SSH Key",

16

value: "ssh_pkey"

17

}

18

]

19

}

20

},

21

password: {

22

title: "SSH Password",

23

input_type: "password",

24

depends_on: {

25

ssh_auth: ["password"]

26

}

27

},

28

ssh_pkey: {

29

title: "SSH Key",

30

input_type: "file",

31

depends_on: {

32

ssh_auth: ["ssh_pkey"]

33

}

34

}

35

};

To make Conditional Rendering work, the Back-end provides the magical depend_on key. There's 2 ways of processing depends_on. If it's across the field/param then the condition will translate as AND. If it's on the value level (the value is given as an Array of string), then it will translate as OR.

argsargs.keydescriptionData type
depends_onkeyThis property represents which param/field that requires a value before the current param/field is evaluated for render criteria.String
depends_onvalueThis property represents what criteria has to be met in order the DDL UI can render the current param/field. It supports String (if single value) or Array of String (if multiple value). Also this property accepts a null value.String
valuedescription
depends_on: \{ field_1: null, field_2: null \};Example of multiple field render condition: Example on the left translates to Only render this field, if both field_1 AND field_2 have value. The null value means that its value doesn’t have to be specific, as long as field_1 and field2 have value. It will render the current param/field.
depends_on: \{ field_1: ["gt", "gte"] \};Example of multiple value render condition: Example on the left translates to Only render this field, if field_1 has value either "gt" OR "gte". The value has to be specific between gt or gte.
depends_on: \{ field_1: ["gt", "gte"], field_2: null \};Example of combined render condition: Example on the left translates to Only render this field, if field_1 has value either "gt" OR "gte" AND field_2 has any value.

Dynamic Options

Dynamic Options is used when the front-end requires an options.data to be defined from the back-end. Dynamic Options is achieved by the dropdown component making a request call to the back end to supply the options data. Dynamic Options have two way of retrieving data. It could be a Fixed Option or Conditional Data Fetching.

The Fixed Options feature is employed when the back-end provides a precise parameter value, making it unnecessary to fetch data, making it a simpler alternative to Conditional Data Fetching.

1

const response = {

2

ssh_auth: {

3

title: "Import method",

4

input_type: "dropdown",

5

options: {

6

display: "alias",

7

value: "name",

8

data: [

9

{

10

alias: "Import table",

11

name: "table"

12

},

13

{

14

alias: "SQL Query",

15

name: "sql"

16

}

17

]

18

}

19

}

20

};

The Conditional Data Fetching however, works differently. It retrieves the data to be rendered from the back-end API. It will make an API request and the request call from the client side will not be executed when certain conditions criteria are not met. This Conditional Data Fetching prevents the request from being sent without the proper request data. Here’s an example:

1

const response = {

2

schema: {

3

title: "Schema",

4

input_type: "dropdown",

5

depends_on: {

6

key: "import_method",

7

value: "table"

8

},

9

options: {

10

dynamic: true,

11

fetch_on: {

12

import_method: ["table"]

13

}

14

}

15

},

16

table: {

17

title: "Table",

18

input_type: "dropdown",

19

depends_on: {

20

key: "import_method",

21

value: "table"

22

},

23

options: {

24

dynamic: true,

25

args: {

26

schema: {

27

value: "from_param",

28

param: "schema"

29

}

30

},

31

fetch_on: {

32

key: "schema"

33

}

34

}

35

}

36

};

param-config-dynamic-options.gif

param-config-dynamic-options.gif

Let’s take a look at the use case above, we want to be able to import a table from a database, but here the problem lies. In order we’re able to import a table, we need to specify the table schema and the table name. The table dropdown input is generated based on the schema dropdown value. This is when Conditional Data Fetching on Dynamic Options comes in handy.

Basically, we tell the front-end to not render (Coincidentally this is chained with Conditional Rendering) and not to fetch the data when the param schema value is empty (Because in order to fetch the table options, schema value is required by the back-end API’s in options.args).

Here’s what happened: in table params, it waits until the schema param has a value, when the fetch condition is met– in which the criteria is based on options.fetch_on.key and options.fetch_on.value, then the front-end makes the API’s (with the options.args value that required by the back-end) request to the back-end. That’s exactly how Conditional Data Fetching works.

Behind the thinking

As any man made logical product, this Param Config has several flaws yet a tolerable one. You might wonder why we chose to create a separate key-value pair for Layouting instead of utilizing data structures. Well, the answer lies in our development process. Initially, we embarked on the journey without a clear picture of how the Param Config should be structured. Our main goal was to create a dynamic solution, and along the way, we gained insights into how we wanted it to work and fit into our application.

To avoid over-engineering and accommodate iterative development, we made a decision that required some compromise. We acknowledged that all our engineers needed to familiarize themselves with the JSON Renderer and Param Config first. This approach allowed us to build and refine the feature step by step, ensuring it evolved into the perfect fit for our needs.

Setting boundaries for the Param Config posed the biggest challenge. We wanted the flexibility to make changes swiftly without the need for extensive code replacements or refactoring. Balancing flexibility and structure was key to crafting a solution that truly stood the test of time.


© 2023 irshadibagas.com