React-admin V4: Switching Form Library

React-admin V4: Switching Form Library

React-admin v3 used react-final-form for form handling ; react-admin v4 uses react-hook-form instead. Read on to learn why we made the switch and what it changes for you.

Forms Are Hard

Form handling is a complex beast. Default values, sync, and async validation, validation on submit or on blur, global and per-field errors, field arrays, dependent fields, parsing and formatting, auto-save, handling of boolean and number inputs, integration with UI libraries, performance… React comes a bit short when it comes to form handling.

React-admin doesn’t manage this complexity by itself. Instead, we rely on a Form library.

It used to be redux-form in react-admin v2, but its author abandoned it and invited users to use react-final-form instead. That’s what we did in react-admin v3. But now react-final-form is not getting much attention from its maintainers, and the bug count keeps growing (360 open bugs at the time of writing). Some of these bugs affect react-admin users, with no solution in sight.

react-final-form contributions are decreasing

We know that managing an open-source library is tedious and not always gratifying, so we’re not going to complain about react-final-form being abandoned. In fact, looking at alternative solutions, we saw that the leader of React form management libraries, formik, is also kind of abandoned (version 2 is no longer maintained, and version 3 has been in alpha since October 2020).

Maybe because form management is hard.

So we were super pleased to discover react-hook-form, a library that has a complete feature set, and a robust codebase. At the time of writing, react-hook-form has only one open issue, which is remarkable. And its maintainers are actively working on it, releasing one new version every week.

react-hook-form contributions are strong and constant

So we made the decision to use react-hook-form in react-admin v4. And to sponsor its lead developer, bluebill1049.

The React-Admin Form API Is (Mostly) Unchanged

We replaced the library used by react-admin forms without changing the react-admin form API - for the most part. The same code works in react-admin v3 and v4:

const PostCreate = () => (
  <Create>
    <SimpleForm>
      <TextInput source="title" validate={required()} />
      <TextInput source="teaser" fullWidth multiline />
      <RichTextInput source="body" fullWidth validate={required()} />
      <ReferenceInput source="author" reference="users">
        <AutocompleteInput optionText="name" />
      </ReferenceInput>
    </SimpleForm>
  </Create>
);

Maintaining backward compatibility proved challenging, especially for advanced features. We decided to embrace the react-hook-form API so that users don’t have to learn two ways of working with forms (react-admin’s and react-hook-form’s). This means there are some breaking changes, documented in the react-admin v4 Upgrade Guide. The most striking is that initialValues is now called defaultValues, but the upgrade should be as simple as a search and replace.

And beyond the internal handling of forms, react-hook-form opens many new possibilities.

Validation By Schema

react-hook-form allows to use external validation libraries (such as Yup, Zod, Joi, Superstruct, Vest and many others) for form validation. React-admin supports this feature through the <Form resolver> prop.

For instance, to validate a Customer form with Yup:

import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { SimpleForm, TextInput, NumberInput } from "react-admin";

const schema = yup
  .object()
  .shape({
    name: yup.string().required(),
    age: yup.number().required(),
  })
  .required();

const CustomerCreate = () => (
  <Create>
    <SimpleForm resolver={yupResolver(schema)}>
      <TextInput source="name" />
      <NumberInput source="age" />
    </SimpleForm>
  </Create>
);

Uncontrolled Inputs For Better Performance

By default, react-admin inputs are controlled, meaning their value changes as the user edits the input value. Something like:

import { useState } from "react";

const NameForm = ({ onSubmit }) => {
  const [name, setName] = useState("");

  const handleChange = (event) => setName(event.target.value);

  const handleSubmit = (event) => {
    onSubmit({ name });
    event.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" value={name} onChange={handleChange} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
};

But controlled components have a cost: in a naive implementation, the entire form renders at every keystroke. This becomes a problem when the form contains many controls. react-hook-form solves this by supporting uncontrolled components, where form data is handled by the DOM itself. Something like:

import { useRef } from "react";

const NameForm = ({ onSubmit }) => {
  const input = useRef();

  const handleSubmit = (event) => {
    onSubmit({ name: input.current.value });
    event.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" ref={input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
};

You can leverage the register function from the FormContext to register uncontrolled components and improve form performance:

import { useFormContext } from "react-hook-form";

const NameInput = () => {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <input
      id="name"
      aria-invalid={errors.firstName ? "true" : "false"}
      {...register("name", { required: true })}
    />
  );
};

Built-In DevTools

If you need to inspect the form state, no need for console.log()! react-hook-form comes with developer tools:

react-hook-form devtools

Simply add the DevTool component to your form:

import { DevTool } from "@hookform/devtools";

const PostCreate = () => (
  <Create>
    <SimpleForm>
      <TextInput source="title" validate={required()} />
      <TextInput source="teaser" fullWidth multiline />
      {/* Add the devtool inside the form */}
      <DevTool />
    </SimpleForm>
  </Create>
);

Conclusion

This article only scratches the surface of all the features supported by react-hook-form, so I can only recommend you to dive into their great documentation.

react-hook-form

By switching to react-hook-form in react-admin v4, we made react-admin forms more robust, more flexible, and faster. Especially for complex forms with dependent inputs and value formatting, our experience is that react-admin feels more intuitive and that we never get blocked. Overall, react-hook-form increases the react-admin developer experience a lot. I’m sure you’ll love it!

Authors

François Zaninotto

Marmelab founder and CEO, passionate about web technologies, agile, sustainability, leadership, and open-source. Lead developer of react-admin, founder of GreenFrame.io, and regular speaker at tech conferences.

Comments