Editor’s note: This post was updated 1 November 2021.
The more modern the application, the more likely developers will need to use special features and helpful hints to ensure demanding clients are happy with their user experience.
In the world of React, forms give us all the power of input components — but this power isn’t enough.
We need better and faster ways to create customized components involving inputs, selects, buttons, and potentially new components that are not recognizable by our browsers implicitly (i.e., create new UI experiences in terms of components).
We need to validate data in many different forms, sometimes via complex regex or JavaScript validation functions, and sometimes through external resources. In certain cases, one might need to store data locally in the navigator and recover it wisely. Others might need to communicate with those components in their own way.
React doesn’t give developers a way to deal with custom highlights, so the community came up with ways to do it themselves.
There are dozens of different options, such as libs for basic form manipulation, integration with Redux, and the like. At this point, the best option for users seems to be Formik — at least that’s what the numbers show us.
The image below displays the most downloaded npm packages for famous React form libraries (as per the writing of this article) at npmtrends.com:
Formik is by far the favorite. Not only is it flexible and communicative with React, but it also allows developers to easily integrate with Yup (a popular JavaScript object schema validator and object parser).
Perhaps its most important feature is form state management — we no longer need to keep calling the Redux store’s state on every keystroke (which is indeed bad practice) because state is automatically maintained by Formik locally.
While Formik is good with controlled components, it’s not as adept at handling uncontrolled ones.
Unform, on the other hand, is focused on offering high performance for React forms and nested structures (particularly deep ones). Unform also allows you to create strong relationships between your components — even the uncontrolled components — without sacrificing anything performance-wise. This Brazilian React library also works very well with React Hooks.
In this article, we’re going to look at a few examples that demonstrate the potential of this library.
First, let’s take a look at how both libraries handle form creation. Below, we can see a basic Formik form usage:
import React from 'react'; import { Formik } from 'formik'; const SampleForm = () => ( <div> <Formik initialValues={{ email: '', password: '' }} validate={values => { const errors = {}; // error validations here return errors; }} onSubmit={(values, { setSubmitting }) => { setTimeout(() => { console.log(values); // submit logic here setSubmitting(false); }, 400); }} > {({ values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, }) => ( <form onSubmit={handleSubmit}> // fields {errors.email && touched.email && errors.email} <button type="submit" disabled={isSubmitting}> Submit </button> </form> )} </Formik> </div> ); export default SampleForm;
Refer to the official React docs for React specifications. Most of these libraries usually advise that developers start with the JavaScript submit function.
In our case, this function has two parameters: values
, which represents the form fields’ values, and a second object with properties and functions from Formik for free use in the submit body function.
The setSubmitting
(a boolean), for example, is a useful mechanism for analyzing whether the request is currently happening or not.
Each Formik form is made of the main element <Formik>
and some important props:
initialValues
: the local state value for each subsequent controlled componentvalidate
: receives all the form’s values as parameters. You can use this function to perform whatever validations you want. You can also use it to set and return the proper error codes/messagesonSubmit
: determines which function will handle the submit eventrender
: the implicit form render function itself. Decide which are the controlled Formik components and which are the uncontrolled HTML components of your formPretty simple, isn’t it? Let’s take a look at the equivalent Unform form below. Make sure to have react
, react-dom
, and yup
packages already installed.
import React from 'react' import { Form } from '@unform/web' import { useField } from '@unform/core' import * as Yup from 'yup'; function Input({ name, ...rest }) { const inputRef = React.useRef(null) const { fieldName, defaultValue, registerField, error } = useField(name) React.useEffect(() => { registerField({ name: fieldName, ref: inputRef, getValue: ref => { return ref.current.value }, setValue: (ref, value) => { ref.current.value = value }, clearValue: ref => { ref.current.value = '' }, }) }, [fieldName, registerField]) return <input ref={inputRef} defaultValue={defaultValue} {...rest} /> } function SampleForm() { const formRef = React.useRef(null); const initialValues = { email: '', document: '' }; async function handleSubmit(data) { try { // Remove all previous errors formRef.current.setErrors({}); const schema = Yup.object().shape({ email: Yup.string() .email() .required(), password: Yup.string() .min(6) .required(), }); await schema.validate(data, { abortEarly: false, }); // Validation passed console.log(data); } catch (err) { const validationErrors = {}; if (err instanceof Yup.ValidationError) { err.inner.forEach(error => { validationErrors[error.path] = error.message; }); formRef.current.setErrors(validationErrors); } } } return ( <Form ref={formRef} initialData={initialValues} onSubmit={handleSubmit}> <Input name="email" type="email" /> <Input name="password" type="password" /> <button type="submit">Sign in</button> </Form> ) } render( <> <SampleForm /> </> )
First off, we need to install the Unform via:
yarn add @unform/web @unform/core # or npm i @unform/web @unform/core
Now we need to import the respective Form
and Input
components from Unform. The second thing you’ll see is related to a Yup validation schema.
Like Formik, Unform easily integrates with Yup schemas by providing a schema
property you can later use to validate the form input values. Because Yup is, by far, the most popular lib for input values validation, it’s pretty straightforward to use.
This code provides a single example to help you better understand Unform with validations like email, required fields, and minimum value length.
When Unform works with Hooks, the class-based component style is abandoned in favor of a single-function component.
The initialValues
from Formik translates to initialData
here — make sure to match each object property to each input name to ensure values are applied correctly. The handleSubmit
function loses the parameters from Formik and simply receives the values for manipulation in the submit event.
Lastly, there’s no internal render
function, which means that <Form>
must be mixed with other components.
You can also use other common properties like placeholder
, style
, etc.
Let’s analyze a second example with combo boxes, which are pretty common elements we need in forms.
Select dropdowns usually look like this in Formik:
<Field as="select" name="color"> <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </Field>
Not complex. Unform, on the other hand, partners with react-select to make this happen. Below’s an example of how to combine an external dropdown component with Unform:
import React, { useRef, useEffect } from 'react'; import ReactSelect, { OptionTypeBase, Props as SelectProps, } from 'react-select'; import { useField } from '@unform/core'; interface Props extends SelectProps<OptionTypeBase> { name: string; } export default function Select({ name, ...rest }: Props) { const selectRef = useRef(null); const { fieldName, defaultValue, registerField, error } = useField(name); useEffect(() => { registerField({ name: fieldName, ref: selectRef.current, getValue: (ref: any) => { if (rest.isMulti) { if (!ref.state.value) { return []; } return ref.state.value.map((option: OptionTypeBase) => option.value); } if (!ref.state.value) { return ''; } return ref.state.value.value; }, }); }, [fieldName, registerField, rest.isMulti]); return ( <ReactSelect defaultValue={defaultValue} ref={selectRef} classNamePrefix="react-select" {...rest} /> ); };
Pay special attention to the useRef
function that connects the field with its ReactSelect
counterpart.
When it comes to multiple and nested elements, no library provides a fully adaptable and working solution.
Formik has a very handy object called <FieldArray>
, which helps with common array/list manipulations:
let countries = ['andorra', 'argentina', 'aruba']; <Form> <FieldArray name="countries" render={arrayHelpers => ( // defining your form, loop through `countries` )} /> </Form>
It also has a bunch of familiar functions like pop
, replace
, push
, insert
, and others for the automatically injected arrayHelpers
that help with item manipulation.
However, whenever you want to nest items and apply validations or organize the forms in a way that’s closer to your entity model, Formik lacks options.
Unform has an interesting mechanism for dealing with nested objects. Take the following code as an example:
import React from 'react'; import { Form, Input, Scope } from '@rocketseat/unform'; function App() { function handleSubmit(values) { console.log(values); } return ( <Form onSubmit={handleSubmit}> <Input name="name" /> <Scope path="address"> <Input name="country" /> <Input name="zipCode" /> </Scope> <button type="submit">Submit</button> </Form> ); }
Scope
is an Unform component that marks the root of your nested element. It’s just for markup purposes and doesn’t hold any value.
When you submit the form your values
object would look like this:
{ name: '', address: { country: "", zipCode: "" } }
Whenever you update the state’s value, it will reflect on your form fields.
When choosing between Formik and Unform, it’s all about finding the best fit for your project purpose.
Unform is a great library, especially because it’s lightweight, performative, and flexible enough to allow integration with other libraries. You may want to use a third-party component in your forms, like react-select and react-datepicker. With Unform, that’s easy to do.
Go ahead and try it yourself. Migrate some components, or create components from scratch. Make use of React Hooks for more concise code, and test a different fields organization for nested elements.
And don’t forget to check the official docs for more on the other elements, as well as examples of each one.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
One Reply to "Comparing React form builders: Formik vs. Unform"
Thanks for the intro to Unform, Diogo. Definitely one to check out.
Quick one with regards to nested field schemas: Formik does offer handy lodash-style field names:
https://jaredpalmer.com/formik/docs/guides/arrays#nested-objects
A lesser-known feature, but have found it super handy in the past.