Disclaimer
Not a code guide of either of the mentioned tools, but rather my experience using these, the problems I faced and the solution we adopted.
TL;DR
We opted Formik to build forms in our app, everything was going fine until we had a long dynamically generated form. With as few as ~100 fields, it started to lag a lot. Than I stumbled upon lot of issues Formik has (both open and closed) about speed issues and double renders, so decided to try something else.
React-final-form was next in the list, It turned out to be great choice with a lot of fine grained control options, speed was a lot better, we also enabled validateOnBlur
, which made it lighting fast. We had to build a few wrappers for shipify-polaris components in use. You can find the code/s below.
Long version
We started building a shopify embedded app with Rails 6
and ReactJS
. I was using react_rails
gem for the purpose of binding the two.
For those of you who don’t know, shopify provides a library of UI components among some guidelines to build shopify plugins/apps keeping the experience as close to original shopify experience as possible.
As shopify plugins are rendered in an iframe, that means anyone can use whatever he wants for the UI/backend and pretty much everything. This can result in completely different UI components and their look and feel across different plugins. This is where shopify-polaris comes in to unify the user experience across different plugins.
Shopify provides official react and html versions of this, but their guidelines can be followed independently of the UI framework.
ENOUGH OF THE SHOPIFY, WHERE ARE FORMS!
OHK! I hear you. Lets get right into forms.
Building forms with formik
Formik is a nice small size library, it is supposed to support all common cases and does not present itself as one stop shop for all kind of form needs. And they are right. We build small forms with relatively simple Yup validation schema and it was working great. There was no lag or anything like that when editing the form.
Than we had to create a dynamic form which could have N sections, and each section will have minimum of ~35 fields.
Building schema for this complex and conditional form was also interesting but that’s not topic here.
Our normal form had around 100+ fields. When we did our development and testing on sample form of 1 section, everything was working fine. We used a small helping library @satel/formik-polaris to bind shopify components and formik without any extra work.
After everything was built, when we tested this against real world loads of forms with 3-4 sections. It showed huge lag when editing. We would see text changing after a complete second of stopping the key-presses. This was obviously un-acceptable to deliver the feature. Here is when our debugging journey began.
Trying to fix lags while sticking with formik
As I mentioned earlier, we had a complex schema with conditional logics, array of objects and so on. We knew this is one place where the bottleneck is, formik is validating whole form on a single key-press, but it did not justify this much of the lag.
One other issue was multi rendering , it was re-rendering whole form minimum of 2 times on single key press. Which of-course means a lot of CPU load.
Memoization
Initially we suspected it is the re-renders which is causing the main issue, so we did split our components in smaller chunks with memoization in mind. And used React.memoize
to make them pure and stop their re-renders. but despite moving a huge chunk of form to memoized versions, there was little to no effect on lag.
Try to reduce re-renders to 1
There were multiple issues which we found during our debugging on formik about multiple re-renders, with very few ever resolved, and that either didn’t help us anyway. So we were stuck with multiple re-renders.
At this point we were so frustrated with this experience and saw number of open issues about speed on formik’s large forms that we were fully convinced that formik speed issue is a real thing , and we need to move forward and try something else. This is when I saw a suggestion on an issue comment in formik to use react-final-form and we thought why not?
Replacing formik
with react-final-form
React final form’s first impression from docs and readme was that it is built to be one-stop-shop for all kind of forms, which means it has a lot of built in fine grained controls for all kind of use cases. And it is async
by default. Which means it runs validations async reducing any possibilities of lags due to validations.
React-final-form
even has a brief dedicated guide to migrate from formik. So i don’t need to add those details. I will only add details which are specific to shopify-polaris.
So as we were using @satel/formik-polaris which binds polaris components onChange
and error
type properties to formik.
I could not find anything similar for react-final-form
which meant i had to write my own wrappers. Which are not a big deal but its always nice to have plug-able solutions instead of writing your own.
Here is a gisti created with code for the wrappers/adapters and notes on their usage.
Using Yup validation schema with react-final-form
There is apparently no offical way to use a validation schema in react-final-form
while formik
had this support. I found a function somewhere in an issue on github. And that worked flawlessly for us, here is the final form of that function we used:
import { get, set } from 'lodash-es'
// For extracting errors per field for formik
export const convertYupErrorsToFieldErrors = (yupErrors) => {
return yupErrors.inner.reduce((errors, { path, message }) => {
if (errors.hasOwnProperty(path)) {
set(errors, path, get(errors, path) + ' ' + message);
} else {
set(errors, path, message);
}
return errors;
}, {});
}
// And to use yup schema for validation:
export const finalFormYupValidator = async (values, schema) => {
try {
await schema.validate(values, { abortEarly: false });
} catch (errors) {
return convertYupErrorsToFieldErrors(errors);
}
}
And to use this:
import { finalFormYupValidator } from '../../helpers'
...
...
<Form
initialValues={initialValues}
validate={async (values) => await finalFormYupValidator(values, ValidationSchema)}
onSubmit={async (values) => await submit(values, alert) }
Obviously you may tune above according to your needs.
Fine tuning react-final-form
for our usage
As soon as we made switch to react-final form
, we saw immediate effect of at-least 4-5x speed improvement , we could sense a little bit of lag still but it was a lot better already.
We decided to fix this lag too, so we explored other options. As our form was considerably big, we knew validations are what is causing this remaining lag. So we enabled validateOnBlur
option(by passing it as a prop to Form
and voila! Our form was as fast as it could get with no lag at all.
Top comments (0)