Every day we see a huge number of web applications allowing us customizations. It involves drag & drop or metadata-driven UI interfaces to support multiple layouts while having a single backend. Feedback taking system is one of the simplest examples of such products, where on the admin side, one can manage the layout and on the consumer side, users are shown that layout to capture the data. This post focuses on building a microframework to support such use cases with the help of React and Formik.
Building big forms in React can be extremely time consuming and tedious when structural changes are requested. Handling their validations also takes too much time in the development life cycle. If we use Redux-based solutions to simplify this, like Redux-form, we see a lot of performance bottlenecks. So here comes Formik!
"Why” is one of the most important questions while solving any problem. There are quite a few reasons to lean towards Formik for the implementation of such systems, such as:
- Advanced validation support with Yup
- Good community support with a lot of people helping on Github
Being said that, it's one of the easiest frameworks for quick form building activities. Formik’s clean API lets us use it without worrying about a lot of state management.
Yup is probably the best library out there for validation and Formik provides out of the box support for Yup validations which makes it more programmer-friendly!!
We need to follow certain API structures to let our React code understand which component to render where.
Let’s assume we will be getting responses from the backend API in the following fashion.
We can have any number of fields but each one will have two mandatory unique properties type and field. We will use those properties to build UI as well as response.
So let’s start with building the simplest form with React and Formik.
You can view the fiddle of above code here to see the live demo.
We will go with the latest functional components to build this form. You can find more information on useFormik hook at useFormik Hook documentation.
It's nothing more than just a wrapper for Formik functionality.
Adding dynamic nature
So let's first create and import the mocked API response to build the UI dynamically.
You can view the fiddle here.
We simply imported the file and made it available for processing. So now, we need to write the logic to build components dynamically.
So let’s visualize the DOM hierarchy of components possible:
We can have a recurring container within the container, so let's address this by adding a children attribute in API response.
You can see the fiddle with response processing here with live demo.
To process the recursive nature, we will create a separate component.
You can view the complete fiddle of the recursive component here.
So what we do in this is pretty simple. We pass config which is a JSON object that is retrieved from the API response. We simply iterate through config and build the component based on type. When the type is an array, we create the same component RecursiveContainer which is basic recursion.
We can optimize it by passing the depth and restricting to nth possible depth to avoid going out of stack errors at runtime. Specifying the depth will ultimately make it less prone to runtime errors. There is no standard limit, it varies from use case to use case. If you are planning to build a system that is based on a compliance questionnaire, it can go to a max depth of 5 to 7, while for the basic signup form, it's often seen to be only 2.
So we generated the forms but how do we validate them? How do we enforce required, min, max checks on the form?
For this, Yup is very helpful. Yup is an object schema validation library that helps us validate the object and give us results back. Its chaining like syntax makes it very much easier to build incremental validation functions.
Yup provides us with a vast variety of existing validations. We can combine them, specify error or warning messages to be thrown and much more.
You can find more information on Yup at Yup Official Documentation
To build a validation function, we need to pass a Yup schema to Formik.
Here is a simple example:
You can see the schema usage example here.
In this example, we simply created a schema and passed it to useFormik hook. You can notice now unless and until the user enters the name field, the form submission is not working.
Here is a simple hack to make the button disabled until all necessary fields are filled.
You can see how to use submit validation with live fiddle here
We do get a vast variety of output from Formik while the form is being rendered and we can use them the way it suits us. You can find the full API of Formik at Formik Official Documentation
So existing validations are fine but we often get into cases where we would like to build our own validations. How do we write them and integrate them with Yup validations?
For this, there are 2 different ways with Formik + Yup. Either we can extend the Yup to support the additional validation or pass validation function to the Formik. The validation function approach is much simpler. You just need to write a function that gives back an error object to Formik. As simple as it sounds, it does get messy at times.
So we will see an example of adding custom validation to Yup. Yup provides us an addMethod interface to add our own user-defined validations in the application.
Let’s say we want to create an alias for existing validation for supporting casing because that’s the most common mistake we see. Url becomes url, trim is coming from the backend as Trim. These method names are case sensitive so if we say Yup.Url, it will fail. But with Yup.url, we get a function. These are just some examples, but you can also alias them with some other names like I can have an alias required to be as readable as NotEmpty.
The usage is very simple and straightforward as follows:
This will create an alias for url as URL.
Here is an example of custom method validation which takes Y and N as boolean values.
With the above, we will be able to execute yup.string().stringBoolean() and yup.string().StringBoolean().
It's a pretty handy syntax that lets users create their own validations. You can create many more validations in your project to be used with Yup and reuse them wherever required.
So writing schema is also a cumbersome task and is useless if the form is dynamic. When the form is dynamic then validations also need to be dynamic. Yup’s chaining-like syntax lets us achieve it very easily.
We will consider that the backend sends us additional following things with metadata.
validationType will hold the Yup’s data types like string, number, date, etc and validations will hold the validations that need to be applied to that field.
So let's have a look at the following snippet which utilizes the above structure and generates dynamic validation.
You can see the complete live fiddle with dynamic validations with formik here.
Here we have added the above code snippets to show how easily we can add a new method to Yup. Along with it, there are two functions createYupSchema and getYupSchemaFromMetaData which drive the whole logic for building dynamic schema. We are passing the validations in response and building the validation from it.
createYupSchema simply builds Yup validation based on the validation array and validationType. getYupSchemaFromMetaData basically iterates over the response array and builds Yup validation for each field and at the end, it wraps it in the Object schema. In this way, we can generate dynamic validations. One can even go further and create nested validations with recursion.
It's often seen that adding just another field is time-consuming in the traditional approach of writing the large boilerplate for forms, while with this approach, it eliminates the need for hardcoding the fields and allows them to be backend-driven.
Formik provides very optimized state management which reduces performance issues that we generally see when Redux is used and updated quite frequently.
As we see above, it's very easy to build dynamic forms with Formik. We can save the templates and even create template libraries that are very common with question and answer systems. If utilized correctly, we can simply have the templates saved in some NoSQL databases, like MongoDB and can generate a vast number of forms quickly with ease along with validations.
To learn more and build optimized solutions you can also refer to <FastField> and <Field> APIs at their official documentation. Thanks for reading!