Implement a Layered Architecture by React Hook Form (v7)
Tags: react, reacthookform, typescript
Published: 2021-09-16 09:29:22

I will talk about the idea of component design using React Hook Form (v7).

[React Hook Form](https://react-hook-form.com)

React Hook Form is a library for writing validation logics for forms based on Hooks.

With the separation of the form logic in Hooks, it should be possible to implement the **View layer** written in TSX and the **Logic layer** responsible for validation separately.

## versions

- React v17
- React Hook Form v7
- Material UI v5

## An example of TextArea component

In this section, we will consider the case of implementing a TextArea component.

### View layer

First, we will implement a simple component that does not depend on React Hook Form. We also use [Material UI](https://material-ui.com/ja/getting-started/installation/) as a UI framework.

```tsx
import { FormHelperText, TextareaAutosize, TextareaAutosizeProps } from '@material-ui/core';
import type { ChangeEventHandler, FocusEventHandler } from "react";

export type TextAreaProps = {
  error?: string;
  className?: string;
  placeholder?: string;
};

export const TextArea = (
  props: TextAreaProps & {
    inputRef: TextareaAutosizeProps['ref'];
    value: string;
    onChange: ChangeEventHandler<HTMLTextAreaElement>;
    onBlur: FocusEventHandler<HTMLTextAreaElement>;
  }
) => {
  return (
    <>
      <TextareaAutosize
        minRows={3}
        placeholder={props.placeholder}
        className={props.className}
        ref={props.inputRef}
        value={props.value}
        onChange={props.onChange}
        onBlur={props.onBlur}
      />
      {!!props.error && <FormHelperText error>{props.error}</FormHelperText>}
    </>
  );
};
```

We have deliberately divided props into `TextAreaProps` and non-TextAreaProps, the intention of which will be clarified in the next section.

### Logic layer

In the logic layer, we create a separate wrapper component that wraps a simple text area defined as the View layer with the logic of the Form.

```tsx
import { DeepMap, FieldError, FieldValues, useController, UseControllerProps } from 'react-hook-form';

import { TextArea, TextAreaProps } from '~/components/parts/form/textarea/TextArea';
import formControlStyles from '~/components/parts/form/FormControl.module.scss';
import classNames from 'classnames';

export type RhfTextAreaProps<T extends FieldValues> = TextAreaProps & UseControllerProps<T>;

export const RhfTextArea = <T extends FieldValues>(props: RhfTextAreaProps<T>) => {
  const { name, control, placeholder, className } = props;
  const {
    field: { ref, ...rest },
    formState: { errors },
  } = useController<T>({ name, control });

  return (
    <TextArea
      inputRef={ref}
      className={classNames(formControlStyles.formInput, formControlStyles.formTextArea, className)}
      placeholder={placeholder}
      {...rest}
      error={errors[name] && `${(errors[name] as DeepMap<FieldValues, FieldError>).message}`}
    />
  );
};
```

The component naming is prefixed with Rhf (short for React Hook Form), and the type and other components are dependent on React Hook Form.

The CSS is also imported from a style file dedicated to form controls, named `FormControl.module.scss` (*If it receives a className, the parent can change its appearance in any way, which is both good and bad). ).

If you use the `useController` hook, you can get the various values needed for the form component, and you can pour them almost directly into the TextArea component.

The `TextAreaProps` type is also used for Props in logic layer components. For example, `className` is passed from Form and relayed to the bottom View layer. I put these relayed types in `TextAreaProps`.

### Form layer

Finally, we will show how to actually use the component we created from the form. We'll call this the Form layer.

First, we will get the control variable of the Form from the `useForm` hook.

```tsx
  const {
    control,
    handleSubmit,
    setError,
    formState: { isValid },
  } = useForm<NewPostInput>({
    mode: 'onChange',
    resolver: yupResolver(newPostSchema),
    defaultValues,
  });
```

And pass `control` to the `RhfTextArea` component.

```tsx
    <RhfTextArea placeholder="post content" name="body" control={control} />
```

This allows us to do a bit of dependency injection.
On the `RhfTextArea` component side, we can take the `control` of any form, pass it to `useController`, and see the status of that form.

```tsx
  const {
    field: { ref, ...rest },
    formState: { errors },
  } = useController<T>({ name, control });
```

Since `formState` has about 10 properties other than errors, each component can also get the state of the form.
For example, it may be easy to implement disabling a form component when `isSubmitting = true`.

```typescript
export declare type FormState<TFieldValues> = {
    isDirty: boolean;
    dirtyFields: FieldNamesMarkedBoolean<TFieldValues>;
    isSubmitted: boolean;
    isSubmitSuccessful: boolean;
    submitCount: number;
    touchedFields: FieldNamesMarkedBoolean<TFieldValues>;
    isSubmitting: boolean;
    isValidating: boolean;
    isValid: boolean;
    errors: FieldErrors<TFieldValues>;
};
```

## Points

### Benefits of carving out layers

What are the advantages of separating components in different layers?

The biggest one is that you can use the text area in places other than forms.

It's hard to imagine using a text area in a place other than a form, but for example, a Select box might be used to change the sort order in a list screen. In other words, the implementation of displaying a text area on a form can be divided into two parts: "displaying the text area" and "binding events and styles to it according to the purpose of the form", so that the former can be used more universally.

Another benefit is that it helps to keep dependencies on libraries in order.

If you take a look at the components in the View layer again, you will see that they only depend on Material UI and React:

```ts
import { FormHelperText, TextareaAutosize, TextareaAutosizeProps } from '@material-ui/core';
import type { ChangeEventHandler, FocusEventHandler } from "react";
```

Then, looking at the logic layer, we can see that it depends only on `react-hook-form`.

```ts
import { DeepMap, FieldError, FieldValues, useController, UseControllerProps } from 'react-hook-form';

import { TextArea, TextAreaProps } from '~/components/parts/form/textarea/TextArea';
import formControlStyles from '~/components/parts/form/FormControl.module.scss';
```

This separation of library dependencies by hierarchy reduces the number of places to look for large updates or library migrations in the future.

## References

- https://koprowski.it/react-native-form-validation-with-react-hook-form-usecontroller/
- https://zenn.dev/erukiti/articles/webform-2021
- https://suzukalight.com/snippet/posts/2021-04-08-react-native-hook-form-yup

---

For those who have seen this article.

I would be happy to exchange React insights with you, please follow my dev.to account and [GitHub account](https://github.com/TeXmeijin).