🏁 Final Form: The road to the checkered flag
Two and a half years ago, about a month after Dan Abramov’s unforgettable launch of Redux at React Europe 2015, I released a humble little library that managed form state in Redux, called Redux-Form, which, over the intervening period, has grown quite substantially in popularity.
As I write this in late 2017, Redux-Form has well over 8k stars on Github and 800k monthly downloads on NPM. By my recent calculations, based entirely on NPM download figures, about half of React projects use Redux, and about a quarter of those use Redux-Form, so about 11% of the 7 million projects that download React, also download Redux-Form.
With this popularity and usage has come a deluge of feedback. Everyone has had a slightly different use case and requested a slightly different behavior out of Redux-Form. I have done my best to accommodate these, and merge in pull requests for features that never would have occurred to me. I’ve done a lot of thinking about forms and form state management.
Problems with Redux-Form
Over these years, I have heard four primary complaints about Redux-Form. For some reason, unlike with most complaints, I have most heard these in person at conferences than on Twitter or Github or Stackoverflow. They are as follows, in no particular order, but with growing emoji angst:
1. 🙁 It’s only for React?
“Redux works with Angular, et. al., it’s too bad that Redux-Form doesn’t.”
2. ☹️ I have to use Redux?
“I really need a form solution, but my project isn’t using Redux.” Also, everyone high up in the React community, including both inventors of Redux, say that Redux is not the best place to keep form data. Oops.
3. 😖 I can’t use inline render functions?
“Why does my input lose focus on first keypress?” Well, it’s because Redux has asynchronously updated both your input and the surrounding form component (because now
dirty === true), and because your inline render function is
!== the previous one, blah blah blah 😢
4. 😭 It’s over 26k gzipped? No way!
“Bundle size, bro. I’m watching my weight.”
v5 of Redux-Form became very popular, the biggest complaint I had was that it was slow. The theory behind why was that it was rerendering the entire form on every single keypress, since the
values were provided to the form component.
In early 2016, I solved this, by completely rewriting it from scratch, in what became
v6, creating a
<Field/> component, which only updated when its state changed, but did not update when another field’s state changed. This complicated matters greatly, because now the whole
<Form/> only received a few select form-wide props, so the people that needed more information had to
connect() with a suite of selectors to get other form state that was no longer provided by default.
It is entirely possible that the fear of rerendering the entire form drummed into me by the complaints that lead to Redux-Form
v6 is unfounded, but, whether or not the user notices it, it must be true that rerendering parts of your tree needlessly is going to be slower than avoiding it. I decided that my next form solution should have built-in “only rerender if you need to” logic, which most people will never need, but that will allow fine tuning of complex forms.
Going with the Flow
Normally I would build a library from the inside out, from the core to the public API, but this time, I defined the API first, even writing some tests, before implementing the core functionality. By strongly typing my API with Flow, I was able to catch bugs and ensure that the internals were satisfying the contracts of the API. It felt a little like pair programming with an annoying partner that was constantly saying, “Well actually, that’s supposed to return a string!”, but in a good way! Flow is awesome. 😍
Before I get to the finish line of this post, I’d like to talk about the process of choosing library names. I probably tried a hundred different names involving the word “form” before finding one that was not taken already on NPM. So many of them are rotten unused/unfinished/undocumented libraries that have not been updated in a long time, and others are just empty namespace squatters — a tactic I was forced to resort to in reserving the name of my library until launch. I guess it was inevitable that a good NPM library name would become as hard to acquire as a good .com domain, but it’s still frustrating.
Speaking of namespace…Emojispace! After seeing the emoji branding succcesses of popular 2017 libraries Styled Components 💅, Glamorous 💄, and Downshift 🏎️, I decided that my library needed an emoji to go with it. I have been very nervous that someone might steal the one I chose (months ago) before launch, which has been very motivating!
Okay, now on to the announcement…
🏁 Final Form
Today I am launching a new form library, called 🏁 Final Form. It’s based on the Observer pattern, so observers can subscribe to receive updates for either form or field state changes. Each observer must explicitly define which parts of the state they want to be notified about, and they will only be called when the parts they have subscribed to have been changed.
It has zero dependencies, is framework agnostic, and weighs in at 💥 3.5k gzipped 💥.
🏁 React Final Form
Of course it had to come with a companion library, 🏁 React Final Form, which is a thin wrapper around 🏁 Final Form that allows it to be used in React. And when I say thin… it weighs in at 💥 2.2k gzipped 💥, for a combined weight of 5.7k gzipped. It has no dependencies, and only two peer dependencies: 🏁 Final Form and React…as the name would suggest 🙄.
Problems with Redux-Form: ✅ Solved!
- 🙂 Is it only for React?
Nope! There’s nothing stopping anyone from implementing a 🏁 Final Form wrapper in Angular, Ember, Vue, Preact, Elm, ReasonML, TheNextHotness™, etc.
2. 😊 Do I have to use Redux?
Nope! State is canonically kept inside the 🏁 Final Form instance, and the React components know when to rerender by calling
setState() when they are notified of a state change.
3. 😀 Can I use inline render functions?
Of course you can! It’s almost 2018, for Pete Hunt’s sake! And to paraphrase a prominant thoughtleader of the React community:
“OMG!! Render functions! RENDER FUNCTION ALL THE THINGS!!!11one11"
<Field/> components use the three ways of rendering that I have humbly stolen from the awesome rival library, Formik, by Jared Palmer. Brilliant idea. You can either pass a
render prop, or a render function to the
children prop (see docs).
4. 😍 It’s only 5.7k gzipped? 21% the size of Redux-Form? AWESOME!
Lean and mean. 😎
What does it look like?
People familiar with the Redux-Form syntax should feel right at home, in fact many custom component libraries that have been adapted for Redux-Form should Just Work™ in 🏁 React Final Form. I will eventually write a Migration Guide, but I haven’t gotten around to that yet.
🏁 Final Form is not yet up to feature parity with Redux-Form. For example, at launch, it’s missing the concept of
<FieldArray/> to edit an array of values (EDIT:
<FieldArray/> functionality released 7 days after this post) and
<Fields/> to edit several fields in a single component. Nor does it have some of the more esoteric features such as
autofill. I plan on being a little more willing to say, “No, I will not implement your specific feature,” with this library, to minimize its complexity and surface area. I suspect that most edge cases that previously required a new Redux action could be managed within React state.
I very much look forward to working with others who want to wrap 🏁 Final Form into another view framework, or even other implementations of 🏁React Final Form. One suggestion: get in early if you want to be the dominant library (see first paragraph).
I cannot wait to get feedback from the community; I have learned so much from you all in my years with Redux Form. ❤️
I do plan on continuing to maintain Redux Form. If you’ve built a huge enterprise application on top of Redux Form, don’t worry; I’m not going anywhere. But maybe you could try out 🏁 Final Form on the next form page you build?
🙏 Thanks for reading. Go install it, star it, and give it a try! 🎉