Comparing React Form Libraries: SurveyJS, Formik, React Hook Form, React Final Form And Unform
This article has been kindly supported by our dear friends at SurveyJS. A product suite of open-source JavaScript client-side components designed to simplify the creation of a full-cycle form management system fully integrated in your IT infrastructure.
![SurveyJS](https://files.smashing.media/partners/surveyjs/surveyjs-logo.png)
Working with user input has always been one of the most vital parts of developing any website. Handling things like form validation, submission, and displaying errors can become complex, so using existing form solutions may be the way to go, and there are several solutions for dealing with forms in React.
In this article, we will look at SurveyJS, Formik, React Hook Form, React Final Form and Unform. We will compare how they are used, how we can integrate them into custom UI components, and how to set up dependent fields with them. Along the way, we will also learn how to validate forms using Yup, a JavaScript object schema validator.
This article is useful for those who want to know the best form libraries to use for future applications.
Note: This article requires a basic understanding of React and Yup.
Should You Use A Form Library? #
Forms are an integral part of how users interact with the web. They are used to collect data for processing from users, and many websites today have one or more forms. While forms can be handled in React by making them controlled components, it can become tedious with a lot of repetitive code if you build a lot of forms. You have an option of reaching out to one of the many form libraries that exist in the React ecosystem. These libraries make it easier to build forms of varying complexity, as they provide validation and state management out of the box, among other useful form-handling features.
Factors To Be Considered #
It is one thing to know the different form libraries available, and another to know the appropriate one to use for your next project. In this article, we will examine how these form libraries work and compare them based on the following factors:
- Implementation
We will look at their APIs, and consider how easy it is to integrate them into an app, handle form validation, submission, and the overall developer experience. - Usage with Custom Components
How easy is it to integrate these libraries with inputs from UI libraries like Material UI? - Dependent Fields
You may want to render a form field B that depends on the value of a field A. How can we handle that use case with these libraries? - Learning Curve
How quickly can you start using these forms? How much learning resources and examples are available online? - Bundle Size
We always want our applications to be performant. What tradeoffs are there in terms of the bundle size of these forms?
We will also consider how to make multi step forms using these libraries. I didn’t add that to the list above due to how I structured the article. We will look at that at the end of the article.
React Form Library by SurveyJS #
SurveyJS Form Library is an open-source client-side component that renders dynamic JSON-driven forms in React applications. It uses JSON objects to communicate with the server. These objects, also known as JSON schemas, define various aspects of a form, including its style, contents, layout, and behavior in response to user interactions, such as data submission, input validation, error messages, and so on.
The library has native support for React. It is free to use and is distributed under the MIT license. The SurveyJS product family also includes a self-hosted JSON form builder that features drag-and-drop UI, a CSS Theme Editor, and a GUI for conditional logic and form branching.
Features #
- It’s suitable for multi-page forms, quizzes, scored surveys, calculator forms, and survey pop-ups.
- Compatible with any server & database.
- Integration demos for PHP, ASP.NET Core, and NodeJS.
- All data is stored on your own servers; therefore, there are no limits on the number of forms, submissions, and file uploads.
- 20+ accessible input types, panels for question grouping, dynamic questions with a duplicate group option.
- Input validation, partial submits & auto-save, lazy loading, load choices from web services.
- Custom input fields
- Carry forward responses, text piping, autocomplete
- Integration with 3rd-party libraries and payment systems
- Support for webhooks
- Expression language (Built-in & Custom Functions), data aggregation within a form
- Auto-localization and multi-locale surveys, support for RTL languages
- Weekly updates
- 120+ starter demos & tutorials
How To Install #
How To Use #
SurveyJS Form Library for React consists of two npm packages: survey-core
(platform-independent code) and survey-react-ui
(rendering code). Run the npm install survey-react-ui --save
command to install survey-react-ui
. The survey-core package will be installed automatically as a dependency. Another advantage of SurveyJS is its seamless integration with custom UI libraries and their form components. This dedicated guide demonstrates how to integrate the React Color component to a basic SurveyJS form.
To add SurveyJS themes to your application, open the React component that will render a form and import the Form Library style sheet import 'survey-core/defaultV2.min.css';
. This style sheet applies the Default theme. You can also apply a different predefined theme or create a custom one.
Next, you need to create a model that describes the layout and contents of a form. Models are specified by model schemas (JSON objects). SurveyJS website offers a full-featured JSON form builder demo that you can use to generate JSON schemas for your forms. In this example, model schema declares two textual questions, each with a title and a name. Titles are visible to respondents, while names are used to identify the questions in code. To instantiate a model, pass the model schema to the Model constructor as shown in the code above.
To render a form, import the Survey
component, add it to the template, and pass the model instance you created in the previous step to the component’s model
attribute.
As a result, you should see the following form:
Formik #
Formik is a flexible library. You can choose to use Formik with native HTML elements or with Formik’s custom components. You also have the option of setting up your form validation rules or a third-party solution like Yup. It allows you to decide when and how much you want to use it. We can control how much functionality of the Formik library we use.
Formik takes care of the repetitive and annoying stuff — keeping track of values, errors, visited fields, orchestrating validation, and handling submission — so you don’t have to. This means you spend less time setting up form state and onChange
and onBlur
handlers.
Installation #
Implementation #
Formik keeps track of your form’s state and then exposes it plus a few reusable methods and event handlers (handleChange
, handleBlur
, and handleSubmit
) to your form via props
. You can find out more about the methods available in Formik here.
While Formik can be used alongside HTML’s native input fields, Formik comes with a Field
component that you can use to determine the input field you want, and an ErrorMessage
component that handles displaying the error for each input field. Let’s see how these work in practice.
In the code above, we are working with four input fields, an email, a select, a checkbox, and a radio field. Form
is a small wrapper around an HTML <form>
element that automatically hooks into Formik’s handleSubmit
and handleReset
. We will look into what withFormik
does next.
withFormik
is a HOC that injects Formik context within the wrapped component. We can pass an options
object into withFormik
where we define the behaviour of the Formik context.
Formik works well with Yup in handling the form validation, so we don’t have to set up custom validation rules. We define a schema for validation pass it to validationSchema
. With mapPropsToValues
, Formik transfers the updated state of the input fields and makes the values available to the App
component through props as props.values
. The handleSubmit
function handles the form submission.
Usage With Custom Components #
Another benefit of Formik is how straightforward it is to integrate custom UI libraries and their form components. Here, we set up a basic form using Material UI’s TextField
component.
Field
hooks up inputs to Formik automatically. Formik injects onChange
, onBlur
, name
, and value
props to the TextField
component. It does the same for any type of custom component you decide to use. We pass TextField
to Field
through it’s as
prop.
We can also use Material UI’s TextField
component directly. The docs also provide an example that covers that scenario.
Dependent Fields #
To set up dependent fields in Formik, we access the input’s value we want to track through the values
object in the render props. Here, we are tracking the remember
field, which is the checkbox, and rendering a message based on the state of the field.
Learning Curve #
Formik’s docs are easy to understand and straight to the point. It covers several use cases, including how to use Formik with third-party UI libraries like Material UI. There are also several resources from the Formik community to aid your learning.
Bundle Size #
Formik is 44.4kb minified and 13.1kb gzipped.
![Formik bundle size](https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/799ba83d-9fac-4d0c-8124-44bd99041a59/2-comparing-react-form-libraries.png)
React Hook Form #
React Hook Form, or RHF is a lightweight, zero-dependency, and flexible form library built for React.
Installation #
Implementation #
RHF provides a useForm
hook which we can use to work with forms.
We start by setting up the HTML input fields we need for this form. Unlike Formik, RHF does not have a custom Field
component, so we will use HTML’s native input fields.
RHF supports Yup and other validation schemas. To use Yup with RHF, we need to install the @hookform/resolvers
package.
Next, we have to configure the RHF setup and instruct it to use Yup as the form validator. We do so through the resolver
property useForm
hook’s configuration. object. We pass in yupResolver
, and now RHF knows to use Yup to validate the form.
The useForm
hook gives us access to several form methods and properties like an errors
object, and the handleSubmit
and register
methods. There are other methods we can extract from useForm
. You can find the complete list of methods.
The register
function connects input fields to RHF through the input field’s ref
prop. We pass the register
function as a ref
into each element we want RHF to watch. This approach makes the forms more performant and avoids unnecessary re-renders.
The handleSubmit
method handles the form submission. It will only run if there are no errors in the form.
The errors
object contains the errors present in each field.
Usage With Custom Components #
RHF has made it easy to integrate with external UI component libraries. When using custom components, check if the component you wish to use exposes a ref
. If it does, you can use it like you would native HTML form elements. However, if it doesn’t you will need to use RHF’s Controller
component.
Material-UI and Reactstrap’s TextField
expose their inputRef
, so you can pass register
to it.
In a situation where the custom component’s inputRef
is not exposed, we have to use RHF’s Controller
component.
We import the Controller
component from RHF and access the control
object from the useForm
hook.
Controller
acts as a wrapper that allows us to use custom components in RHF. Any prop passed into Controller
will be propagated down to the Checkbox
.
The render
prop function returns a React element and provides the ability to attach events and value into the component. This simplifies integrating RHF with custom components. render
provides onChange
, onBlur
, name
, ref
, and value
to the custom component.
Dependent Fields #
In some situations, you may want to render a secondary form field based on the value a user puts in field a primary form field. RHF provides a watch
API that enables us to track the value of am input field.
Learning Curve #
Asides from its extensive and straightforward documentation that covers several use cases, RHF is a very popular React Library. This means there are several learning resources to get you up and running.
Bundle Size #
RHF is 26.4kb minified and 9.1kb gzipped.
![React Hook Form bundle size](https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/bca85d1a-c396-4894-b9de-95f05522f7b6/4-comparing-react-form-libraries.png)
Final Form #
Final Form is a framework-agnostic form library. However, its creator, Erik Rasmussen, created a React wrapper for Final Form, React Final Form.
Installation #
Implementation #
Unlike Formik and React Hook Form, React Final Form (RFF) does not support validation with Object Schemas like Yup out of the box. This means you have to set up validation yourself.
The Form
component is a special wrapper provided by RFF that manages the state of the form. The main props
when using RFF are onSubmit
, validate
, and render
. You can get more details on the form props RFF works with.
We start by setting up the necessary input fields. render
handles the rendering of the form. Through the render props, we have access to the FormState
object. It contains form methods like handleSubmit
, and other useful properties regarding the state of the form.
Like Formik, RFF has its own Field
component for rendering input fields. The Field
component registers any input field in it, subscribes to the input field’s state, and injects both field state and callback functions, onBlur
, onChange
, and onFocus
via a render prop.
Unlike Formik and RHF, RFF does not provide support for any validation Schema, so we have to set up custom validation rules.
The validate
function handles the validation for the form. The onSubmit
function will be called with the values of your form when the user submits the form and all validation passes. Validation runs on input change by default. However, we can also pass a validateonBlur
prop to the Form
component validation so it also runs on blur.
To display the validation errors, we make use of the meta
object. We can get access to the metadata and state of each input field through the meta
object. We can find out if the form has been touched or has any errors through the meta
’s touched
and error
properties respectively. These metadata are part of the props of the Field
component. If the input fields have been touched, and there is an error for we display that error.
Usage With Custom Components #
Working with custom input components in RFF is straightforward. Using the render props method in the Field
component, we can access the input
and meta
of each input field.
RFF’s Field
component bundles all of the props that your input component needs into one object prop, called input
, which contains name
, onBlur
, onChange
, onFocus
, and value
. The input
prop is what we spread to the TextField
component. The custom form component you plan on using must support these props in order to be compatible with RFF.
Alternatively, we could render Material UI’s TextField
using the component Field in RFF. However, we won’t be able to access the input
and meta
data using this method.
Dependent Fields #
The values
object can be accessed from the render prop. From here, we can track the state of the remember
field, and if true
, render a message, or whatever use case fits your app’s needs.
Learning Curve #
Compared to other form libraries, RFF does not have as many learning resources. Also, the docs do not go into detail to show how RFF can be used with Yup or any other validation schema, and that would be a helpful addition.
Bundle Size #
RFF is 8.9kb minified and 3.2kb gzipped.
![React Final Form bundle size](https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a633d545-3ee7-4947-85f6-41277a08e43d/6-comparing-react-form-libraries.png)
Unform #
A core aspect of Unform’s API design is how straightforward it is to hook form inputs to Unform. Initially, Unform came with its built-in input components, however, it no longer follows that pattern. This means you have to register each field you want Unform to track. We can do that with the registerField
method Unform provides.
Installation #
Implementation #
The first step in using Unform is registering the input fields we want the library to track.
The useField
hook is the heart of Unform. From this hook, we get access to the fieldName
, defaultValue
, registerField
, error
, and more. Let’s see what they do and how they work.
fieldName
: a unique field name.defaultValue
: the default value of the field.registerField
: this method is used to register a field on Unform. When registering a field, you can pass some properties to theregisterField
method.error
: the error message of the registered input field.
Whenever the input component loads, we call the registerField
method in the useEffect
to register the input. registerField
accepts some options:
name
: the name of the field that needs to be registered.ref
: the reference to the field.getValue
: this function returns the value of the field.
We pass the methods; fieldName
, defaultValue
, clearError
and any other props to the input component. clearError
clears the error of an input field on focus if there is any. This is how we register input fields with Unform. We do the same thing for the select, checkbox, and radio input fields.
Now that we have registered the input fields, we have to bring everything together.
We import the Form
component from Unform and set up a form reference for the Form
component. We get access to several helpful methods from this reference.
Now that we’ve registered the input fields, created a form reference, and set up the form, the next step is to handle the form validation and submission.
Unform supports validation with Yup, so we create a schema
. By default, the schema’s validate(
) method will reject the promise as soon as it finds the error and won’t validate any further fields. So to avoid that you need to pass the abortEarly
option and set the boolean to false { abortEarly: false }
. We use the setErrors
method from the form reference we created to set the errors for the form if any.
Similar to the handleSubmit
function that handles submit validation, a handleChange
function can be created that will handle form validation as the user types.
Both validation functions work the same way, so the logic for the submit and onChange
validation are the same. However, there are some differences. Unlike validation on submit, where we can get access to and validate the form data with handleSubmit
, we need a way to handle the input’s data in handleNameChange
. To do that, we set up a form state where we will store the input’s value. Then as the user types, we update the form state through setForm
. Now that we have access to the form data, we validate it in the same manner we did in handleSubmit
. Lastly, we have to pass handleNameChange
to the input’s onChange
prop, which we do.
You will note that in handleNameChange
, I created a different validation schema than the one I used in handleSubmit
. I did this so there will be two different errors to make the onChange
and onSubmit
error different. However, in a real-world project, you would use the same validation schema.
The sandbox below provides a demo for validation on input change.
Usage with Custom Components #
We integrate third-party UI components with Unform the same way we do with native HTML inputs, through the registerField
method.
Dependent Fields #
Unform currently does not provide any watch functionality to help in creating dependent fields. However, there are plans in the library’s roadmap to create a useWatch
hook for tracking the form state.
Learning Curve #
Unform is not the easiest library to get started with. The process of registering input fields is not developer-friendly compared to other articles. Also, validation and accessing errors
object in Unform is more tedious than it should be. It also doesn’t help that there is no way to access form values to set up dependent fields. It doesn’t come with a lot of features that would be needed when working with forms, and there are very few resources out there that show different use cases in using Unform. However, the documentation provides a few examples.
Overall, it is not the most straightforward library to use. I believe more work can be done in providing a better documentation.
With that being said, Unform is still under development, and the team is currently working on new features.
Bundle Size #
Unform is 10.4kb minified and 3.7kb gzipped.
![Unform bundle size](https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/73a35903-4823-4e5a-97af-4648c9ec9e5c/5-comparing-react-form-libraries.png)
Creating Multi Step Forms #
I have created multi-step form demos for each library. I left this to the last section because the implementation for the libraries remains similar. The only different thing is the multi-step form UI. Let’s see the structure of the UI.
Breaking Down The Form Wizard #
For the multi step wizard, let’s see the file structure and how it works.
Let’s look at the App.js
file.
Here, we define a formStep
state, which holds the state of the current step of the form wizard. We also define prevFormStep
and nextFormStep
functions to go back and forth in the form wizard.
Next, we pass the formStep
state prevFormStep
to the FormCard
. This will enable us to go a step backward and also display the current step of the form.
Finally, we pass formStep
and nextFormStep
to the form components. We conditionally render each form based on the value of formStep
. We use >=
to render the forms because we want the forms to remain rendered even though it’s step has been passed. This makes tracking the form values easier.
Now the FormCard
component. This is the container for each form.
FormCard
does 3 things: conditionally render the back button based on the value of formStep
, show the value of the current step to the user as a form of progress tracker, and render its children, which is the current form being displayed.
The form components have a similar structure. Let’s see PersonalInfo
.
In the root div
, we conditionally apply showForm
and hideForm
styles to show or hide the form. We do this because of how we implemented the form wizard.
We display a particular form based on the value of formStep
. However, we use the >=
conditional to conditionally render a form. This means that all the forms will render as formStep
increases. We want them to render, but also be hidden. That’s why we conditionally apply the showForm
and hideForm
styles.
Finally, we have the handleSumbit
. Let’s look into how that works.
Handling form submission starts with creating a context to store the values of the forms.
setFormValues
is a function that takes the data from each form and uses those values to update the state of data
, which will hold the values of each form.
We can access the form data and the setFormValues
function from useFormData
.
In each form, we pull setFormValues
from useFormData
and pass in the values of the form. This way, as we submit each form, the data is being stored.
Finally, if the form has been successfully filled, the FormCompleted
component renders.
The core aspect of creating the multi-step form is the wizard. The form validation and submission for each library are the same as what we covered above. I attached these sandbox demos for integration with each library.
Summary #
In comparing these 4 form libraries, we have considered different factors. The image below is a tabular representation of how these libraries stand against each other.
![Comparison summary table](https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/520b8dcf-5f3b-4259-9b38-4be496ebc33a/1-comparing-react-form-libraries.png)
I use either Formik or RHF in handling forms in my projects. These are my top choices because they are the most popular, have the clearest and most extensive documentation, and the most learning resources in terms of YouTube videos and articles.
Conclusion #
Forms will remain a critical part of how users interact with the web, and these libraries, among others, have done a good job in creating form management solutions for the common developer use cases, and much more.
Resources #
- SurveyJS Documentation
- Formik Docs
- React Hook Form Docs
- React Final Form Docs
- Unform Docs
- Yup Docs
- React Form Validation With Formik And Yup by Nefe Emadamerho-Atori for Smashing Magazine
![Smashing Editorial](https://www.smashingmagazine.com/images/logo/logo--red.png)
— Comments 1
What a great article, thanks!