JSDev logotype

#Forms #Formik #Performance

Formik's Performance In Complex Forms

Working on a reporting app that centers around a complex form taught me about the short comings of Formik.

Avatar
Dennis GaidelOctober 6, 2021

Professionally I've been working on a reporting tool for the past year. The frontend of that tool consists only of a couple of views and one of them is a relatively complex form, which is also the heart of this reporting tool.

Challenges

It consists of a lot of features that make the implementation of a form quite challenging. There are dropdown fields filled with items dynamically loaded from an API endpoint (1).

Other dropdowns show a list of items based on the selection of a dependent field (2).

The money input fields format the number based on the chosen locale while keeping an unformatted, pure JavaScript number in the state. Other money input fields depend on one or multiple money input fields and are sometimes calculated automatically (3,5). To make things harder, some calculations are bidirectional between multiple fields.

Imagine a price value and a commission percentage. Multiplying those values equals the value of the commission input. Should the change of the commission now change the percentage or the price value?

And to make things even more exciting, complete parts of the form are exchanged based on other fields (4).

Simplified mockup of the complex form

Formik

It makes sense to choose a library to handle all the internals of form state handling, validation and so on not to reinvent the wheel. Formik is such a library: It has earned more than 28.5k stars on GitHub (link) and is even mentioned briefly in the React documentation (link).

Formik comes with a couple of appealing features: Support for a custom validation mechanism, provides hooks to read from and update the state as well as a couple of standard handlers for onChange and onBlur. It also has a component to deal with lists and offers functions to deal with them accordingly (e.g. adding and removing items).

With more than twenty fields with such special behaviors as described previously, each of which reacts to every key press, a form library is challenged not only in terms of its functionality, but also in terms of performance.

Performance

And indeed an input lag was noticeable which led to the discovery of Formik's FastField component (link):

<FastField /> is an optimized version of <Field /> meant to be used on large forms (~30+ fields) or when a field has very expensive validation requirements.

The problem is that every field update causes the whole form state to be rewritten. This on the other hand triggers every field that is either rendered with the <Field /> component or uses the useField() hook.

This is calling for trouble and the sheer co-existence of <FastField /> raises the question, why the other components are not optimized for performance from the beginning on.

One a half years ago a GitHub issue was opened to raise awareness to the performance issues that come with the useField() hook, but it's not resolved. According to a GitHub user commenting in this thread (link) Formik's architecture makes it nearly impossible to implement an efficient solution. Instead he proposes to rely on React.memo to avoid unnecessary rerenders.

Unfortunately that's close to the solution we were forced to apply in order to prevent those costly rerenders: Intead of wrapping the useField() hook, which is smart, we decided to wrap our custom input field components, like the money input field and the dropdowns for example.

Conclusion

Formik offers a clean documentation, a great set of hooks and components to get the job done, and is a sufficient solution for smaller forms. Due to the serious performance bottlenecks in complex forms and the lack of improvement over the course of the past one and a half years, a choice in favor of Formik should be considered carefully.

React Final Form (link) is another library that offers "high performance subscription-based form state management":

For small forms, redrawing your entire form on every keypress is no problem. But when your form grows, performance can degrade.

No other form library allows such fine tuning to manage exactly which form elements get notified of form state changes.

Its API is quite similar to Formik in a lot of components, and therefore it's relatively easy to switch to this solution. There is even a migration guide available (link).

Additional features, like decorators (link), only add to its value and reduce the amount of code that needs to implemented manually otherwise.