How to create a basic implementation of React Hook Form
Tags: javascript, react, programming
Published: 2022-11-09 08:42:48

## Introduction

I was introduced to [React Hook Forms](https://react-hook-form.com/) a couple of weeks ago by a coworker as he was explaining to me how they are using it in their team. My first thought was `Who uses a library just for managing forms?`, up until now I've just used `useState` or similar solutions to store the form state and I was fine with the results.

But then I started checking their documentation and found an interesting thing when you changed an input field it doesn't re-render the form…


![With React Hook Forms](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bg0waezdfshivdhh0oa5.gif)



In comparison this is a normal one:


![Normal React form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bi64ry8t2pmm2wwbqh7i.gif)



How are they managing to do so? I wondered… So I started checking their code and this is how it works:

## Why does a normal form gets re-rendered when you type a new value

First let's see a normal form, one that uses `useState` to store the value

``` javascript
function MyForm() {
    const [firstName, setFirstName] = useState("")

    return <form>
        <input name="firstName" value={firstName} onChange={(e) => setFirstName(e.target.value)} />
    </form>
}
```

Every time you type something you trigger a function that changes the value of `firstName` and thus **trigger a re-render**. The thing is that you don't need in most cases to store this value right away because this value is anyway stored in the HTML.

## What is the React Hook Form approach?

React exports many hooks to us, like `useState` and `useRef`, the latest is usually popular to hold references to DOM elements, but it's more than that, you can think of the `useRef` hook as a way to have `instance variables` in a React component, this means that you can have the same value preserved across re-rendering **and changing it won't trigger a re-render**.

In that order of ideas, in most cases, it makes sense to store the values of a form in a ref and this is basically what `React Hook Forms` do.

## Creating a simple replica of React hook forms

To illustrate better this idea, let's create a basic implementation of `React Hook Forms`, let's call it `React Mini Forms` 😛

### Creating a useForm hook

The first thing we will need is to create a normal function that will be used as a hook. This function will return the following properties:

- `register(fieldName: string)` This function will be called everything we want to add a new field to the `form`
- `handleSubmit()` This function can be used to handle `onSubmit` in forms, it will return the current values of the fields in the form
- `watch(fieldName: string)` In some specific cases, we want to do something when a certain field in a form changes, that's when we want to use this function

We will store the form values in a `ref` because as we said earlier, every time we change the value this won't retrigger a render, inside the `useForm` hook, let's put

``` javascript
// useForm.js
import { useRef, useState } from 'react'

export function useForm() {
    const state = useRef()
  const [_, setFormState] = useState({ renderCount: 0 }) // Will explain later

    // This will be the ref storing the current values of the fields
    if(!state.current) {
        state.current= {
            fields: new Map(),
            watching: new Map(),
        }
    }

    return {
        register: function(fieldName) {
        },
        handleSubmit: function() {
        },
        watch: function(fieldName) {
        }
    }
}
```

### Consuming the hook

Now in our React Component, we will use the hook in the following way

``` javascript
import { useForm } from './useForm'

function App() {
    const { register, handleSubmit, watch } = useForm()

    console.log("The current value of the lastName is: ", watch("lastName"))

    const onSubmit = (formData) => {
        console.log("Data: ", formData)
    }

    return <form onSubmit={handleSubmit(onSubmit)}>
        <input {...register("firstName")} />
        <input {...register("lastName")} />

        <button>Submit</button>
    </form>
}
```

### Implementing the register function

The register function as we said earlier will be the one in charge of adding new fields to the form state In this function, we will add the `field` to the list of fields of the form (If it's not already there) and we will return all the normal attributes of inputs like `name`, `onChange`, etc.

``` javascript
register: (name) => {
  if (!state.current.fields.has(name))
    state.current.fields.set(name)

  return {
    name,
    onChange: (event) => {
      state.current.fields.set(
        name,
        event.target.value,
      )
    }
  }
},
```

### Implementing the watch function

This function was the hardest for me to understand, so here's a more basic implementation that works. We have a list of fields that the user is currently "watching". Every time it gets called it adds the field to the watching list if it's not already there and it returns the current value of the field

``` javascript
watch: (name) => {
  if (!state.current.watching.has(name)) {
    state.current.watching.set(name, true)
  }

  return state.current.fields.get(name)
},
```

Now we need to modify the `register` function so every time a value changes and **the field is on the watch list**, we will purposely call a `setState` to tell React that we need to re-render the component

``` javascript
register: (name) => {
    // ....

  return {
      // ...
    onChange: (event) => {
    if (state.current.watching.has(name)) {
        setFormState(state => ({
          renderCount: state.renderCount,
        }))
      }

    }
  }
},
```

### Implementing the handleSubmit function

This is the easier function, it just returns the current value of all fields

``` javascript
handleSubmit: callback => e => {
  e.preventDefault()
  callback(state.current.fields)
},
```

### The final code

All this code should be enough to have a working solution but remember that this is a basic implementation to demonstrate the idea, thus it doesn't have performance improvements and probably it doesn't handle all edge cases.

One thing to take into account when using this approach is that this could be more performance in a complex component `as long as you're not subscribed to all the fields`, if you are subscribed to all the fields a new render will happen every time anyway so a simpler solution would be better.

Let me know any comments you might have!

**Here's the final code** [See it on Codesanbox 📦](https://codesandbox.io/s/react-forms-ng4t4e?file=/src/App.js)

``` javascript
import { useRef, useState } from "react";

export function useForm() {
  const state = useRef();
  const [_, setFormState] = useState({ renderCount: 0 });

  // This will be the ref storing the current values of the fields
  if (!state.current) {
    state.current = {
      fields: new Map(),
      watching: new Map()
    };
  }

  return {
    register: (name) => {
      if (!state.current.fields.has(name)) state.current.fields.set(name);

      return {
        name,
        onChange: (event) => {
          if (state.current.watching.has(name)) {
            setFormState((state) => ({
              renderCount: state.renderCount
            }));
          }

          state.current.fields.set(name, event.target.value);
        }
      };
    },
    watch: (name) => {
      if (!state.current.watching.has(name)) {
        state.current.watching.set(name, true);
      }

      return state.current.fields.get(name);
    },
    handleSubmit: (callback) => (e) => {
      e.preventDefault();
      callback(state.current.fields);
    }
  };
}
```