erikras.com
HomeAbout

๐Ÿ Final Form โ€“ Field Arrays and Mutators

Extending the core functionality of Final Form

Posted in Coding, ๐Ÿ Final Form
December 4, 2017ย -ย 4 min read
Photo by CGP Grey
Photo by CGP Grey

After the release of ๐Ÿ Final Form and ๐Ÿ React Final Form seven days ago, the very first feature that people asked for was array fields / arrays of fields. I could have pretty easily implemented the ability to push, pop, insert, etc. into array values as part of ๐Ÿ Final Form, but the reason that Redux Form got so gargantuan was from exactly this sort of feature bloat, where many individuals added on their own pet feature, and everyone that uses the library has to download all of these features whether they use them or not. How could I avoid this with ๐Ÿ Final Form?

๐Ÿ Final Form is already more or less feature complete (famous last words) as far as editing flat or arbitrarily deep form values. But how could it be made more extensible? Well, hereโ€™s what I came up with:

Mutators

Mutators are functions that can be provided to ๐Ÿ Final Form that are allowed to mutate any part of the form state. You provide an object of mutators when you call createForm(), and then they are exposed (bound to the form state) on the form instance you get back, under form.mutators.whatever(). A mutator function is given: the arguments it was called with, the form state, and a collection of utility functions, like getIn and setIn to read and mutate arbitrarily deep values, as well as changeValue, which updates a field value in the form state. They can mutate the state however they wish, and then the form and field subscribers that need to be notified will be notified.

What does this look like? Well, hereโ€™s a somewhat simplistic example that I created when writing the unit tests:

/** Clears a form value */
const clear = ([name], state, { changeValue }) => {
changeValue(state, name, () => undefined)
}
/** Converts a form value to uppercase **/
const upper = ([name], state, { changeValue }) => {
changeValue(state, name, value => value && value.toUpperCase())
}
const form = createForm({
onSubmit,
mutators: { clear, upper }
})
...
form.mutators.upper('foo')
...
form.mutators.clear('foo')

These are dumb examples, but what else does this allow? Well, for one, it allowsโ€ฆ..

Field Arrays

Everyone doesnโ€™t need to include the code for field arrays in their bundle, but for those that doโ€ฆ..

Introducing:

๐Ÿ Final Form Arrays

This library (687 bytes gzipped) contains mutators for performing array operations on form values in ๐Ÿ Final Form.

import { createForm } from "final-form";
import arrayMutators from "final-form-arrays";
// Create Form
const form = createForm({
mutators: {
// potentially other mutators here
...arrayMutators,
},
onSubmit,
});
// push
form.mutators.push("customers", { firstName: "", lastName: "" });
// pop
const customer = form.mutators.pop("customers");

And of course it needs a companion:

๐Ÿ React Final Form Arrays

This library (1.7 kB gzipped) consists of a <FieldArray/> component that very closely mimics the functionality of Redux Form's <FieldArray/> component, providing a fields object that is very array-like, with methods like forEach, map, push, pop, shift, splice, etc. When you iterate through it, it gives you the array-syntax strings (e.g. foo[0]) for the array value in your form, ready to be used with the <Field/> component.

An example is worth 1,024 wordsโ€ฆ

Going Forward

I have some other ideas of functionality that can be provided by mutators, and Iโ€™m sure you do, too. They could provide some very powerful features to your forms in the future, without imposing feature bloat on applications that just need a simple login and account form.

As always, feedback welcome! Thanks for reading. โค๏ธ

Discuss on Twitter

ยฉ 2023 โ€“ Erik Rasmussen