Hooked-Form v4
Tags: webdev, react, typescript, javascript
Published: 2019-11-28 11:07:34

This is a follow up to [my last post](https://dev.to/jovidecroock/hooked-form-1625)

When coming up with version 4 of [Hooked-Form](https://github.com/JoviDeCroock/hooked-form) I reminded myself what my goals were for Hooked-Form:

- Low bundle size
- High out of the box performance
- Good Developer Experience

in version 3 these were achieved in one way or another but I knew this could be better, so I took a step back and looked at what the possibilities would be.

In the first part I'm going over how Hooked-Form works in a smaller example, in the following parts I'll discuss how I attempted improving the goals for this library.

## How does it work

We don't have to reinvent the wheel, the Form-Field approach used in [redux-form](https://redux-form.com/8.2.2/) is a very good approach and scales well. The fundamentals of the approach have been kept preserved but with the mindset of reducing the bundle size as much as possible.

Let's make a minimal example with Hooked-Form. Let's assume we have a component where you can edit your name and your friends. Our form will have an initial set of values and we can submit it.

```jsx
const Wrapper = ({ children, name, friends }) => {
  const initialValues = React.useMemo(() => ({
    name: props.name,
    friends: props.friends,
  }), [name, friends]);

  return (
    <HookedForm onSubmit={console.log} initialValues={initialValues}>
      {children}
    </HookedForm>
  )
}
```

That's all you need, all [options](https://jovidecroock.github.io/hooked-form/docs/components/hookedForm) can be found here. The `<HookedForm>` will make a `form` tag for you under the hood and bind the onSubmit on it. You might think but what if I want to pass in extra properties? Well any property passed that isn't an option for `HookedForm` will be bound to the `form` tag, this allows you to supply for instance a `className`.

Let's make a `TextField` so we can alter our name in the form.

```jsx
const TextField = ({ fieldId }) => {
  const [{ onChange }, { value }] = useField(fieldId);
  return <input onChange={e => onChange(e.target.value)} value={value} />
}
```

`useField` contains more like `onBlur`, ... To manage the state of a field. The field does not make any assumptions whether you are on a `web` environment so it can be used in `react-native`, ...

If we want to hook up our name we just have to do `<TextField fieldId="name" />` and we're good to go!

Read more about this hook [here](https://jovidecroock.github.io/hooked-form/docs/hooks/useField)

If we want to manage our `friends` field we have the `useFieldArray` hook at our disposal.

```jsx
const Friends = () => {
  const [{ add }, { value: friends }] = useFieldArray('friends');
  return (
    <React.Fragment>
      {friends.map((friend, index) => (
        <div>
          <TextField fieldId={`friends[${i}].name`} />
          <button onClick={() => remove(i)}>Unfriend</button>
        </div>
      )}
      <button onClick={() => add({ id: friends.length })}>Add friend</button>
    </React.Fragment>
  )
}
```

Read more about this hook [here](https://jovidecroock.github.io/hooked-form/docs/hooks/useFieldArray)

All of this should have you set up to manage your friends and your own name, you can see this example in action [here](https://codesandbox.io/s/sweet-poincare-3km8r4k16).

## Developer Experience

We have a pretty well-known approach to this, the Form-Field method for controlled fields, this method works very well and feels very intuitive. We control our state in a central place `Form` and make it available for all others through a `React.contextProvider`. A field can opt-in to a certain field and hook in to the errors, ... for this specific field.

I realized that in some cases you would like to react to changes in another field and adapt the current or possible values to that. Before v4 this would have to be done by adding another `useField` that listened on that field or even a `useFormConnect` which listens to the whole form-state and manually check everything.
Thankfully in v4 we have a solution to that and it's called `useSpy`.

You can read more about useFormConnect [here](https://jovidecroock.github.io/hooked-form/docs/hooks/useFormConnect).

Let's look at an example:

```jsx
import { useField, useSpy } from 'hooked-form';

const optionsForAMinor = [...];
const regularOptions = [...];

const MySelect = () => {
  const [options, setOptions] = useState(optionsForAMinor);
  const [{ setFieldValue }, { value }] = useField('selectField');

  useSpy('age', (newAge) => {
    if (newAge >= 18) {
      setOptions(regularOptions);
    } else {
      setOptions(optionsForAMinor);
    }
  });

  return <Select options={options} value={value} onChange={setFieldValue} />
}
```

Every time our age changes we can change the options without having to mix multiple `useField` hooks in one field.

You can read more about the hook [here](https://jovidecroock.github.io/hooked-form/docs/hooks/useSpy)

## Size + Performance

Before this when a value changed the `Provider` would check what hooks would need to be updated and did that from the `Provider` which in the newer React version will trigger a `console.warn` saying that a parent can't update a child.

This made me reconsider how we handle propagating updates to components, we use the `calculateChangedBits` provided in a `React.createContext` to say we never want to handle rerenders so the value for this becomes `() => 0`. If you are not familiar with this API read more [here](https://hph.is/coding/bitmasks-react-context).

This means that an update to the context value would _never_ trigger any renders, this is not yet what we want but it improves performance since in normal context cases it will trigger a render on every `useContext` even if the changed part is not relevant for them.

The next step here would be to make a small event-emitter that would register on every field. We have a "subject" we can listen to on every field in the form of a `fieldId`, this should be more than sufficient.
Every `useField` will register itself to the emitter with the `fieldId` provided in arguments. When a change is triggered in errors, ... It will look at the changed parts and emit the relevant `fieldIds` causing a render on those hooks.

This compact emitter resulted in a reduction of 200Bytes in size.

## Concluding

I hope I succeeded at improving the Developer Experience, the performance and size parts seem to have improved to me.

If you like the library don't forget to ⭐️ the repository, that means a lot!

Let me know what you think in the comments or [tweet me](https://twitter.com/JoviDeC)!

Bonus example with useSpy: https://codesandbox.io/s/admiring-vaughan-u2lzt