12
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-02-27

はじめに

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

React Hook Formの導入にきっかけ

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

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

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のリファレンスを参考に、進めると完成します。

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に記載内容を参考に、進めると完成します。

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>
  );
}

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つ作成し、顧客の新規登録、顧客の編集、注文の新規登録、注文の編集といった複数の利用シーンでコンポーネントを共通化できるようにします。

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>
    </>
  );
};


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での実装を思いっきりリファクタリングにして(しすぎてみて)、やってみて理解が深まったところだったので、非常によい体験学習でした。

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

12
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?