Why we need another form library (Fielder)

andyrichardsonn

Andy Richardson

Posted on December 12, 2019

Why we need another form library (Fielder)

A few weeks ago, I released Fielder - a form library for React.

That's right, another form library for React competing with the already long-standing leader that is Formik... but hear me out.

This isn't a marketing blog. I'm not going to attempt to brainwash you into using one library over the other. Instead I want to talk about how we currently manage form state, and why I think we needed a change.

Before I begin, I want to give a huge shoutout to all the contributors & maintainers of Formik. It has been the long standing go-to for most (including myself) and has influenced some of the decisions made while creating Fielder.

Forms aren't (always) static

There are a lot of features in Fielder designed to fix issues I had while working with Formik (keep your eyes peeled for a follow up post going into more detail) but the static nature of most popular form libraries I came across was the key reason I felt the need to create Fielder.

When I say "forms aren't static" I'm referring to aspects of a form's state which could change during a user interaction. These include:

1. Fields

Fields in the form's state have the potential to be added, removed and changed.

Most libraries encourage a pattern of statically declaring fields at form construction (e.g. 'initialValues' in Formik).

2. Validation

Just as fields can come and go, so can validation. Changes to form state occur, and validation should have the ability to change, adapt and evolve as necessary.

In formik, while changing of the validation schema isn't easily supported, there are workarounds such as using where conditions in Yup. The challenge with this however is that it needs to be declared up-front. This can be tricky when managing a form which has many possible states.

3. Validity state

A valid form isn't necessarily a form which is ready to be submitted. Instead, a form can be considered valid if the current state allows for progression.

Progression can be a final submission; but it can also be another action such as moving to the next section/page of the form.

The best example of this is a multi-step form where the user has to click next in order to proceed to the next step. If all the current visible fields pass validation the form is valid and the user should be able to move to the next step. Whether or not the form is in it's final, valid, submission-ready state at that point in time is irrelevant.

Field level declaration

Once you're sold on the need for dynamic and evolving forms, field level declarations start to make a whole lot more sense.

Field level declarations allow for adding, removing and changing fields in isolation without having to worry about the wider form state. This is an alternative to a monolithic configuration where all initial values and validation options are declared up front and high up in the component tree.

Configuring a field

With popular libraries such as Formik, you'll be used to a monolithic form config where form and field initialization occur at the same time:

const formConfig = {
  initialValues: {
    firstName: 'Carla',
    lastName: 'Jones',
  },
  validation: Yup.object().shape({
    firstName: Yup.string(),
    lastName: Yup.string(),
  }),
  validateOnChange: true,
};

const formState = useFormik(formConfig);
Enter fullscreen mode Exit fullscreen mode

With field-level declaration patterns (and therefore Fielder), form initialization is isolated.

const formState = useForm();
Enter fullscreen mode Exit fullscreen mode

Forms always start in the same state - empty. It's the responsibility of fields to add, remove and change their value within the form.

const [firstState, firstMeta] = useField({
  initialValue: 'Carla',
  validate: useCallback(
    (value) => Yup.string().validateSync(value), 
    []
  ),
  validateOnChange: true,
});
Enter fullscreen mode Exit fullscreen mode

Working with hooks

Field-level validation ties in really well with React's hooks because the lifecycle of a field corresponds closely to that of a component. Alongside this, because fields can now be declared lower down inside the component tree, we have the ability to access state which is specific to our component.

This allows us to do funky stuff such as this:

const [state, setState] = useState({ isRequired: true });
const [firstState, firstMeta] = useField({

  // Initial value conditional on component props
  initialValue: props.firstName || 'Carla',

  // Validation conditional on component state 
  // (immediately applies on change)
  validate: useCallback(
    (value) => {
      if (state.isRequired && !value) {
        throw Error('First name is required');
      }
    }, 
    [state.isRequired]
  ),

  // Performance optimizations conditional on component state 
  // (immediately applies on change)
  validateOnChange: state.isDesktop
});
Enter fullscreen mode Exit fullscreen mode

Validation that encourages good UX

The progressive and evolving nature of field-level declaration encourages design patterns which follow a similar pattern.

👎 Regression

The valid state of a field on page 1 is conditional on the value of a field on page 2

This is an example of a bad user experience. After already having moved forward on the form, the user now has to backtrack in order to undo an action and there is no obvious way to show the user where the error has occurred.

👍 Progression

The valid state of a field on page 2 is conditional on the value of a field on page 1

or

A field on page 2 is conditionally presented based on the value of a field on page 1

In these examples, the user is informed about actions they can take currently based on the current state. While the user may be able to go back and change previous values, the current state focuses on what the user can do to move forward with the form.

Enforcing these practices

Regressive validation just straight up isn't possible in Fielder. This is because Fielder does not validate inactive fields (fields which are not mounted).

By only calling validation on mounted fields, we also get the added benefit of a better performing form.

Getting started

If you've read this far, congratulations!

To get to grasp with how all this field-level form theory applies to real world usage, check out some of the live Fielder examples.

Also be sure to check out the repo and the official docs site for more in-depth information and to get started.

💖 💪 🙅 🚩
andyrichardsonn
Andy Richardson

Posted on December 12, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related