DEV Community

loading...

Shopify-polaris form building with Formik, React-Final-Form and Yup

ziaulrehman40 profile image Zia Ul Rehman Originally published at ziatechblog.wordpress.com on ・5 min read

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.

Discussion (0)

pic
Editor guide