Creating Dynamic Contract Forms Using ReactJS with Redux

Technology

The structure of RIVIGO’s frontend application for contract creation. This note covers how dynamic forms containing multiple conditional variables were created using ReactJS with Redux.

 

Introduction

At RIVIGO, we are working on an array of challenges to fundamentally transform the logistics industry. These range from creating complex data models for line-haul planning and relay network design to creating AI-based models for fuel pilferage detection systems and so on. A common theme across is that all of these are never-done-before problems that are being solved through first principle thinking and technology.

One such challenge is creating an accurate ‘source of truth’ for the terms of exchange between clients and RIVIGO through the creation of a single exhaustive contract. For any business, understandably such documentation is essential to define a clear scope and set expectations between the two parties. In the logistics industry, this isn’t as straightforward as one might think. In our context, the contract includes several aspects like detention terms, fuel price variance calculations, loading-unloading terms etc.

To avoid any discrepancies arising out of the multiple factors involved, each contract needs to be accurate. Our goal was to create a system that would make the process of contract creation fast, simple and accurate for our business teams. Additionally, as mentioned above, the system should incorporate the hundreds of customisable fields in an efficient manner.

For the knowledge of anyone who is looking to simplify such a business process, this article will cover the structure of RIVIGO’s front end application for contract creation.

 

Building a Dynamic Form to Address the Goal

In simple terms, creating a contract involves taking inputs from the user and submitting them to the server. However, the problem lies in the following:

  • Making input fields interactive and reusable
  • Rendering of nested fields
  • Running validations on user input
  • Sending appropriate data to the server
  • Receiving previous data, particularly the conditional questions, from the server and populating fields on that basis
  • Scalability of the solution for future use cases

We used React with Redux to create our forms in the frontend. This combination works very well with our objectives.

An alternative we had considered was Redux Form. It is a good tool for managing the form states but has a disadvantage. It triggers action every time a field is focused in or focused out and stores multiple properties within each field. Since we had several nested fields for multiple variables, handling them with Redux Forms would have made the system too complex.

 

Approach 

Structure of data

Due to the nature of the problem, defining the schema for our data was an initial challenge. In order to make adding or removing fields easy, we stored our data in a flat-table structure. On the frontend side as well, we designed our redux store in the same way, i.e., a single object which contains information of each and every field.

The snapshot below shows what our store looked like with the Redux development tools.

Creation of fields and bringing HOCs into the picture

Since every field is different in terms of data value, we had to connect each of them with our redux store. A good thing was that the fields were also similar in a few ways like connecting to the store and running validations. This seemed to be a perfect use case for writing higher order components (HOCs) for fields which:

  • connect a field with store on a given key
  • run validations
  • dispatch actions to update the error or value of field

The field-specific logic, for example, rendering a nested field on a value, would go into the component itself.

Below is the structure of our field.

This is what our HOC looks like…

Here, basicField takes two arguments – one is the ‘Field’(react component) and the other is the ‘key’ (determines the corresponding key in redux store). It returns a new field that is now connected, and capable of updating the store on “onChange” event and validating itself. To create a new field, we just define our component with basicField.

Updating the Refreshed Data in the Backend

The next part of the problem entails submitting the collected values to the server. At first, things may seem simple since we have a flat table in the backend. One would assume that you could just submit the collected values and be done. However, that is not the case.

While there are some simple fields, many of the other fields are interdependent, i.e., one field will be activated on the basis of value of some other field. This nesting can get into multiple levels. Let’s understand this with a simple bar menu example.

Question: Liquor Preferences

Options: Whiskey, Beer, Rum, Vodka…

If user selects Whiskey,

Question: Whiskey Brand

Options: Johnnie Walker, Chivas Regal, Dewars…

If user selects Johnnie Walker,

Question: Serving size

Options: 30ml, 60ml, 90ml

Similarly, there can be fields that would be specific to beer or a particular kind of beer.

Here we only have to send the appropriate fields in the backend and discard any other fields. To make this choice (or decision), we created a decision tree. Basically, a decision tree helps us decide which path to choose on the basis of given inputs. Let’s visualize the example above.

Similar to the logic above, creating a decision tree and parsing it with the user inputs solved our problem of field selection.

Here is a snippet from the code for such a decision tree.

Here, null represents the leaf nodes. Nested objects represent the next-level tree on the basis of value selected. For example,

DESSERTS: null, represents that DESSERTS field has not nested values and we could just get the value and move on. Whereas, in case of DRINKS, if the option chosen was ALCOHOLIC, you would get the value of PREFERENCES as WINE or SCOTCH followed by WINE_BRAND or PEG_SIZE and BRAND_NAME.

The next step is to parse the decision tree to prepare our final object that would be sent to the backend. The parsing was fairly simple.

The values array contains the dump of all the choices made by the user. We choose the path as per the choice made by the user and recurse the tree.

The final result contains all the relevant values that must be sent to the server.

 

Conclusion

This approach made our contract creation exercise simple and easy-to-use for our engineers. The same approach can be scaled for creating other complex forms as well by reusing the same HOCs and tree parsing.