React Hook FormとChakraUIを組み合わせて使ってみよう

### はじめに
現在、駆け出しエンジニア同士でチームを結成しアプリ開発をしています。
アプリ開発の過程でChakraUIとReact Hook Formの組み合わせ方法について学んだので、共有します。


### React Hook Formの導入にきっかけ

Reactのフォームバリデーションライブラリとして有名なReact Hook Formですが、当初アプリ開発をスタートした際、この存在を知りませんでした。
そのため、各フォームの項目をuseStateで用意して独自にバリデーションを用意して進めていました。
中間レビューで現役エンジニアにレビューをもらった際に、これではパフォーマンスが悪いので、React Hook Formを導入したほうがいいと指摘があり、そこからReact Hook Form導入の検討を開始しました。

React Hook Form導入前は以下のような形で、これらが各入力に関わるコンポーネントに存在している状況でした。
また新規登録と編集で似たようなページがあるのですが、これもそれぞれコンポーネントを作り、実装してしまっており、共通化されていない状況でした。
イケてない。。。

```react
export const PartnerInput = () => {
  const [name, setName] = useState("");
  const [zipcode, setZipcode] = useState("");
  const [prefecture, setPrefecture] = useState("");
  const [address, setAddress] = useState("");
  const [phone, setPhone] = useState("");
  const [memo, setMemo] = useState("");

```

### React Hook Formを導入する

chakraUIとReact Hook Formを組み合わせて導入する方法は以下に記載があり、単一コンポーネントであれば、比較的簡単に導入できます。

#### chakraUIをインストール

```
yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6
```

#### React Hook Formをインストール

```
yarn install react-hook-form
```

### 単一のコンポーネントで使う場合
フォームに必要なInputコンポーネントとSubmitするコンポーネントが同じ場合、
以下のchakraUIのリファレンスを参考に、進めると完成します。

https://chakra-ui.com/guides/integrations/with-hook-form

```react
import { useForm } from 'react-hook-form'
import {
  FormErrorMessage,
  FormLabel,
  FormControl,
  Input,
  Button,
} from '@chakra-ui/react'

export const PartnerInput = () => {
  const {
    handleSubmit,
    register,
    formState: { errors, isSubmitting },
  } = useForm()

  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <FormControl isInvalid={errors.name}>
        <FormLabel htmlFor='name'>First name</FormLabel>
        <Input
          id='name'
          placeholder='name'
          {...register('name', {
            required: 'This is required',
            minLength: { value: 4, message: 'Minimum length should be 4' },
          })}
        />
        <FormErrorMessage>
          {errors.name && errors.name.message}
        </FormErrorMessage>
      </FormControl>
      <Button mt={4} colorScheme='teal' isLoading={isSubmitting} type='submit'>
        Submit
      </Button>
    </form>
  )
}
```

#### 2つのコンポーネントの組み合わせで使う場合

一方で、新規登録と編集ページが同じである場合やデータA（例：注文）とデータB（例：顧客）の入力画面が同じである場合、コンポーネントを分けて、Inputコンポーネントを共通化したくなってくるかと思います。

その場合は少し記載方法が異なり、以下のReact Hook FormのuseFormContextに記載内容を参考に、進めると完成します。


https://react-hook-form.com/api/useformcontext

```react
import { useForm, FormProvider } from "react-hook-form";

export const Partner = () =>  {
  const methods = useForm();
  const onSubmit = data => console.log(data);

  return (
    <FormProvider {...methods} > 
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        <PartnerInput />
      </form>
    </FormProvider>
  );
}

```

```react
import { useFormContext } from "react-hook-form";
import { FormErrorMessage, FormLabel, FormControl, Input, Button } from "@chakra-ui/react";

export const PartnerInput = () => {
  const {
    register,
    formState: { errors, isSubmitting },
  } = useFormContext();

  return (
    <>
      <FormControl isInvalid={errors.name}>
        <FormLabel htmlFor="name">First name</FormLabel>
        <Input
          id="name"
          placeholder="name"
          {...register("name", {
            required: "This is required",
            minLength: { value: 4, message: "Minimum length should be 4" },
          })}
        />
        <FormErrorMessage>{errors.name && errors.name.message}</FormErrorMessage>
      </FormControl>
      <Button mt={4} colorScheme="teal" isLoading={isSubmitting} type="submit">
        Submit
      </Button>
    </>
  );
};

```

### 3つのコンポーネントで組み合わせる場合

上記で進めると、親のコンポーネントと子のコンポーネントに分けることができるようになりますが、一方で、InputにまつわるFormControlやFormLabelなどをそれぞれのコンポーネントでChakraUIからimportしなくてはなりません。
ここでは、Text入力するためのInputコンポーネントを1つ作成し、顧客の新規登録、顧客の編集、注文の新規登録、注文の編集といった複数の利用シーンでコンポーネントを共通化できるようにします。


```react
import { useFormContext } from "react-hook-form";
import { Button } from "@chakra-ui/react";
import { InputTextForm } from "../molecules/Input/Form/InputTextForm";

export const PartnerInput = () => {
  const {
    register,
    formState: { errors, isSubmitting },
  } = useFormContext();

  return (
    <>
      <InputTextForm
        id="name"
        label="First name"
        placeholder="name"
        isInvalid={errors?.name}
        register={register("name", {
          required: "This is required",
          minLength: { value: 4, message: "Minimum length should be 4" },
        })}
      />
      <Button mt={4} colorScheme="teal" isLoading={isSubmitting} type="submit">
        Submit
      </Button>
    </>
  );
};


```
```react
import { FormLabel, FormControl, Input, Box, FormErrorMessage } from "@chakra-ui/react";

export const InputTextForm = (props) => {
  const { register, id, label, isInvalid, placeholder } = props;

  return (
    <>
      <FormControl id={id} isInvalid={isInvalid}>
        <FormLabel htmlFor={id}>{label}</FormLabel>
        <Box>
          <Input {...register} type="text" placeholder={placeholder} />
          <FormErrorMessage>{isInvalid && isInvalid.message}</FormErrorMessage>
        </Box>
      </FormControl>
    </>
  );
};


```

### 最後に
上記のように共通化することで、コードはすっきりするのですが、一方で、顧客の新規登録・編集で少し違った表現をしなければならなかったり、顧客と注文で違ったりすると、pathでの分岐やpropsの受け渡しがやたら多くなり可読性が落ちるなとも思いました。

この辺はuseStateでの実装を思いっきりリファクタリングにして（しすぎてみて）、やってみて理解が深まったところだったので、非常によい体験学習でした。

アプリリリースまであともう少し、引き続き、頑張っていこうと思います。