Controlled TextFields with React-Hook-Form

Written on 2023-04-27 by Adam Drake - 4 min read

Image of Controlled TextFields with React-Hook-Form

On most apps I build nowadays there is a requirement to gather some user input and this requires a form to work with. There are a great number of things going on in forms and being able to handle that form state easily is a great advantage when developing. This is where react-hook-form really shines for me.

I am a big fan of react-hook-form and use it on many applications I work on. I am also a fan of using component libraries such as Material-UI, Chakra-ui or more recently Mantine.dev. In my opinion there isn't a huge difference in each of these and it more comes down to little design decisions and what is required in the app I am working on. (I realise some developers in the community are very vocal about building your own ui components but when you have business breathing down you neck to get an MVP (Minimal Viable Product) built, you really don't have time to worry about shadings on a button.)

I realise some developers in the community are very vocal about building your own ui components but when you have business breathing down you neck to get an MVP (Minimal Viable Product) built, you really don't have time to worry about shadings on a button.

I won't get into the ins and outs of how to use react-hook-form, if you want to see that then you can check out their docs. I want to focus on working with react-hook-form with a Component library and in this blog post I will be using Mantine.dev.

Controlled Text Fields

In any react application it's good practice to make reusable components where possible and where it makes sense. When working with forms a very common field is the good old `Input` field. When using a react component from a component library in react-hook-form it requires the use of the Controller component from their library. It acts as a wrapper component and makes it easier for you to work with these controlled components.

Below is an example of a reusable Controlled Text Input field:

import { ReactNode } from 'react';
import { Box, Input } from '@mantine/core';
import { Control, Controller } from 'react-hook-form';

export type ControlledTextFieldProps = {
  name: string;
  control: Control<any>;
  label?: string | ReactNode;
  type: string;
  rules?: any;
  disabled?: boolean;
  helperText?: string;
  placeholder?: string;
};

const ControlledTextField = ({
  name,
  control,
  type,
  label,
  rules,
  disabled,
  helperText,
  placeholder,
}: ControlledTextFieldProps) => {

  return (
    <Box my={8} w="100%">
      <Controller
        name={name}
        control={control}
        defaultValue=""
        render={({ field, fieldState: { error } }) => (
          <Input.Wrapper error={error?.message} description={helperText} label={label || name}>
            <Input
              name={name}
              onChange={field.onChange}
              invalid={!!error}
              type={type}
              value={field.value === null ? '' : field.value}
              checked={field.value}
              disabled={disabled}
              placeholder={placeholder}
            />
          </Input.Wrapper>
        )}
        rules={rules}
      />
    </Box>
  );
};

export default ControlledTextField;

Quite a bit in going on here so let me break it down. Let's start with the Controller component from react-hook-form:

<Controller
  control={control}
  name="test"
  render={({
    field: { onChange, onBlur, value, name, ref },
    fieldState: { invalid, isTouched, isDirty, error },
    formState,
  }) => (
    // Component goes here
  )}
/>

This component needs a control prop passed down which comes from the useForm hook. This component also uses render props which is a function that returns a React element and provides the ability to attach events and value into the component. This provides different events such as onChange, onBlur etc to the child component. So when the Controlled Component (say an <Input /> field) is used, you can attach the Controllers onChange event to it. This means react-hook-form can always keep track of the Controlled Components state! Nice!

This component also uses render props which is a function that returns a React element and provides the ability to attach events and value into the component.

There is also a `rules` prop which can be passed down. This enables you to pass down validation rules to the particular field. Something like this:

rules={{ required: true }}

We can then check out the Input field from the top Code Example:

<Input
  name={name}
  onChange={field.onChange}
  invalid={!!error}
  type={type}
  value={field.value === null ? '' : field.value}
  checked={field.value}
  disabled={disabled}
  placeholder={placeholder}
/>

Here we are using the `field` object passed down from the render prop to plug in the Input component. Therefore the value or the onChange event can be connected to the react-hook-form state and it can all be managed from there. So when the form is submitted the current state of each field connected this way will be available.

Conclusion

When it comes to any website, forms can play a crucial role in gathering user data. Having a good toolkit in place for creating forms is crucial to any Frontend Developer. React-hook-form provides a great and reliable library for working with form data and to be able to use this along with a component library enables you to create good looking, well-functioning forms.

Loading...

Adam Drake AI Selfie

Written by Adam Drake

Adam Drake is a Frontend React Developer who is very passionate about the quality of the web. He lives with his wife and three children in Prague in the Czech Republic.

Adam Drakes Site © 2024