Turn Anything Into A Form Field With React Hook Form Controller
Tags: react, forms, reacthookform
Published: 2021-04-12 01:02:43

---
title: Turn Anything Into A Form Field With React Hook Form Controller
published: true
description: Learn how to delight your users using custom form field components with React Hook Form.
tags: react, forms, reacthookform
cover_image: https://elyngved.github.io/blog/cover-dev-rhf-controller.png
---

*Cover image photo by <a href="https://unsplash.com/@chrisjdavis?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Chris J. Davis</a> on <a href="https://unsplash.com/s/photos/computer-form?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>*

[React Hook Form](https://react-hook-form.com/) has quickly become my favorite library to wrangle forms of all shapes and sizes, mainly for its great developer experience. The 30 second screencast on their home page nicely illustrates how to integrate it into a standard form using the magic of `register` to connect each field. When using native `<input/>` components, it's pretty simple to get up and running.

But in the real world, we often don't work with vanilla inputs. Popular UI libraries often abstract and wrap any underlying form elements, making it hard or impossible to use with `register`.

Sometimes we want to delight our users with a custom interactive component, like rating a product with 5 actual star icons instead of a boring select box. How can we connect these to an existing form without messy logic?

# Enter the Controller

The library exports a `<Controller/>` component which was made for exactly this purpose. It allows us to connect any component to our form, enabling it to display and set its value.

To use it, you'll need the `control` object returned from `useForm()` instead of `register`. Also, as usual, you'll need a `name` to tell the form which field we are controlling. Finally, the `render` prop is where we place our component.

```ts
// Controller syntax

const { control } = useForm();

return (
  <Controller
    control={control}
    name="myField"
    render={/* Custom field component goes here */}
  />
);
```

# Making the Field Component

Why is it called `Controller`? It could be because our field component needs to be a *[controlled component](https://reactjs.org/docs/forms.html#controlled-components)*.

In a nutshell, a controlled component is one that gets and sets its current "state" via props. In the case of a form field, that state is the field's current value.

`<input/>` is one example of a component that can be controlled. We tell the input what its current value is, and we give it a way to tell us when that value should be changed.

```ts
// <input/> as a controlled component in a standard React form

const [val, setVal] = useState('')

return (
  <input
    type="text"
    value={val}
    onChange={e => setVal(e.target.value)}
  />
)
```

Here we see the two props required to make our field component work with the Controller:

1. `value` - It should show the current value of the field.
2. `onChange` - It should be able to tell the Controller when a change to the current value is made.

These also happen to be two of the properties handed to us by the `render` function! Its signature includes a `field` object which has `value` and `onChange` (among other things).

It doesn't make much sense to use the Controller for a basic input, but here it is for illustration purposes:

```ts
// Using a basic input in a Controller
// (though you can just use `register` here)

const { control } = useForm();

return (
  <>
    <Controller
      control={control}
      name="myField"
      render={({ field: { value, onChange }}) => (
        <input value={value} onChange={onChange} />
      )}
    />
  </>
)
```

> Note: if you're using React Hook Form V6 or earlier, the function signature here is slightly different. `value` and `onChange` are instead top-level properties of the argument, looking like the following instead.
>
> ```ts
> // V6 or earlier
> render=({ value, onChange }) => (
>   <input value={value} onChange={onChange}  />
> )
> ```

# Real Examples

## Using a UI library: Material UI

Many projects use form inputs from popular UI libraries like [Material UI](https://material-ui.com/). The problem is that any `<input/>` components are usually hidden from us, so we can't use `register` to connect them to our form. This is where Controller comes in!

Often, the fields will use the same `value` and `onChange` prop names. If this is the case, we can simply spread the `{...field}` object into the component.

Other times, the props are not named the same. For example, Checkbox accepts its value as `checked` instead of `value`. This means we can't easily spread `field` into it, but the result is still fairly easy to put together.

```ts
export default function App() {
  const { control, handleSubmit } = useForm({
    defaultValues: {
      textField: "",
      checkbox: false
    }
  });

  const onSubmit = (values) => alert(JSON.stringify(values));

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        control={control}
        name="textField"
        render={({ field }) => (
          // Material UI TextField already supports
          // `value` and `onChange`
          <TextField {...field} label="Text field" />
        )}
      />

      <Controller
        control={control}
        name="checkbox"
        render={({ field: { value, onChange } }) => (
          // Checkbox accepts its value as `checked`
          // so we need to connect the props here
          <FormControlLabel
            control={<Checkbox checked={value} onChange={onChange} />}
            label="I am a checkbox"
          />
        )}
      />

      <Button type="submit" variant="contained" color="primary">
        Submit
      </Button>
    </form>
  );
}
```

[Check out the full example on Code Sandbox](https://codesandbox.io/s/rhf-controller-material-ui-9ik00)

## Building from scratch: a five star rating field

We've all probably used the ubiquitous widget that allows us to rate anything by clicking on a row of star icons. Thankfully, if we are just able to create a controlled component, we can cleanly fit it into the rest of the form.

```ts
// StarButton displays a single star
// It is controlled via active and onClick props
const StarButton = ({ active, onClick }) => (
  <button type="button" onClick={onClick}>
    {active ? <Star color="secondary" /> : <StarBorder />}
  </button>
);

// StarField uses 5 StarButtons to create a field
// with value and onChange props
const StarField = ({ value, onChange }) => (
  <>
    <StarButton active={value >= 1} onClick={() => onChange(1)} />
    <StarButton active={value >= 2} onClick={() => onChange(2)} />
    <StarButton active={value >= 3} onClick={() => onChange(3)} />
    <StarButton active={value >= 4} onClick={() => onChange(4)} />
    <StarButton active={value >= 5} onClick={() => onChange(5)} />
  </>
);

export default function App() {
  const { control, handleSubmit } = useForm({
    defaultValues: {
      rating: 0
    }
  });

  const onSubmit = ({ rating }) => {
    alert(`Your rating: ${rating}`);
  };

  return (
    <Container>
      <form onSubmit={handleSubmit(onSubmit)}>
        <Controller
          control={control}
          name="rating"
          render={({ field }) => <StarField {...field} />}
        />

        <Button type="submit">Submit</Button>
      </form>
    </Container>
  );
}
```

[Check out the full example on Code Sandbox](https://codesandbox.io/s/rhf-controller-5-stars-lyef7)

# Conclusion

Using `<Controller/>` and a properly controlled component, you can make pretty much anything into a form field compatible with React Hook Form. The field can be as simple or fancy as you want, with any logic encapsulated in it, as long as it does these two things:

1. Receive and render the current value/state of the field, commonly through the `value` prop.
2. Call a function when that value should be updated, commonly through the `onChange` prop.