Recommendations that will help you keep your sanity in check when developing React applications with a lot of forms
After two years developing React applications at ProductReview.com.au, I think it’s time for me to share our experiences and the tools we adopted to make us more productive when dealing with forms. I won’t get into user experience in this post because I believe there’s already plenty of good content available.
Note: at the time of writing, the latest redux-form version was v7.3.0.
Wrap third-party code
In a large-scale project, it is very important to create your own interface for third-party libraries being used. It gives you freedom of change. It doesn’t sound that useful in the beginning, but once you have to introduce a feature that the library doesn’t support, it’ll save you hours of refactoring. This is often referred to as Adapter pattern.
Instead of using HTML tags like
<div> in React, we hide them away into
<Image> and so on. This way, we can use more semantic property names and, for example, if the underlying styling implementation changes from raw class names to styled-components, or if we want to use the same components in React Native, the API won’t change. There are a few UI component libraries following this philosophy like Rebass, Cloudfare UI and Atlaskit.
The same pattern can also be applied to forms. Instead of importing things from
redux-form, import them from a file in your project directory like
forms/index.js and enjoy the freedom!
If you need a custom submit handler for every form, or if you need to save the values into localStorage, now it’ll be much easier to add these features without changing every form in your application.
Don’t hide too much of your components’ behaviour. Libraries like React and redux-form already do a lot of that for us, but at least they’re well known and well documented, meaning most people would grasp their ideas quickly. It could be very different with your project. It’s more typing at the end, but at least you will know exactly what’s happening when checking the source code.
Ok, what do you mean in practice?
Imagine you need to render an error message after a form submission fails. A lot of people would be inclined to create a Form component that would to that automatically. The problems with that:
- New developers will have a hard time understanding what’s going on, or even you after a while without touching the code
- You don’t see where the error will be rendered
- It’s harder to customise it. Maybe you want to render a toast notification instead, for example. If you start shoving a lot customisation properties, it can quickly become a monster
React’s success is in big part due to having a small and explicit API (oh, tough old days of two-day data binding 😢). So play safe and make sure your form describes all of its behaviour.
Fields that rely on values from other fields are quite common in most applications. In order to make our lives easier, we developed a component that reads the values from redux-form’s state and passes them as render props to the child:
It is convenient and performant because it won’t re-render the entire form when the value changes. This component is aware of FormSection so you don’t need to worry whether you’re inside of one or not. Ideally this kind of utility would be provided by the library because it uses internal API, but I haven’t had time to submit a proper pull request.
You should be able to easily express all kinds of validation scenarios, including lists of values, nested objects and async checks. We’ve developed a library in-house to deal with that which is unfortunately not open-sourced, but you can start off with validate-redux-form.
Oh, that’s an interesting topic! Fortunately, since the advent of FormSection, things became more elegant, but there’s still a bit of work to do on our side. We create groups of fields like BillingAddressFields or PaymentMethodFields that export everything related to their behaviour (validation, values parsing and so on) so that they can be composed on the root level (the actual form). At the end, it looks like this:
Clear value when the field unmounts
This can be so annoying that it deserves a big heading here in the post. It is pretty useful when you have conditional fields. Get it right from the beginning and create a wrapper around Field (remember the section “Wrap third-party code” above?) with an optional property clearOnUnmount:
Default field values
It’s a very common case, but be careful because the parent component might want to pass initial values. You can merge them together with
Submit on change
Are you tired of googling solutions that actually work? We’ve developed a decorator that deals with that:
Nothing is perfect. Getting server-side rendering right is a very hard task and if it is important for you application like it is for ours, install this Redux middleware that should fix a few issues.
In summary, redux-form is a battle-tested library that is in active development which gives us piece of mind. I’d like to thank Erik Rasmussem and all library contributors and also my workmate Joris Garonian (for promoting the writing of explicit code and encapsulation). With a few tweaks here and there, we can build powerful forms that are clean and maintainable.
By the way, we’re hiring! If you’re up to solving big challenges with cool technology in the sunny and beautiful Sydney, hit us at firstname.lastname@example.org with your résumé.