react-hook-formで自作子コンポーネント(汎用型)を作成

React-Hook-Form自作汎用コンポーネント
◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【5/21開催】Azure OpenAI ServiceによるRAG実装ガイドを公開しました
生成AIを活用したユースケースで最も一番熱いと言われているRAGの実装ガイドを公開しました。そのガイドの紹介をおこなうイベントです!!
https://tech-lab.connpass.com/event/315703/

今回は、「react-hook-formでuseControllerを用いて汎用的な自作コンポーネントを作りたい」という要望をかなえるコードを備忘録的に共有します。自作コンポ―ネントを作成することはよくありますが、汎用型に修正することはなかったので結構勉強になりました。ちゃんとデザインから作っているので、コピペで使えるTailwindコード付き!

ご挨拶

どもども!10月まで猛暑が続くと聞いて絶望した龍ちゃんです。最近は、暑すぎて悲しいですね。エアコンと結婚したんじゃないかと思うぐらいには、ずっとエアコンをつけて過ごしています。先日、家の裏通りでお祭りをしていました。意外と盛大なお祭りだったみたいで、大通りが交通規制でキッチンカーから屋台までいろんなお店が出ていて楽しかったです。

今回は、プロダクト外で作っていた過程で一つ知らないことをしれたので共有していこうと思います。取り扱う内容としては「react-hook-form」になります。もっと言語化すると「react-hook-formでuseControllerを用いて汎用的な自作コンポーネントを作りたい」というものになります。

使っている機能としては、「react-hook-formの基本構文」と「useController」になります。基本的な部分は、公式リファレンスに乗っている部分でカバーすることができます。ですが、自作コンポーネントを作成する部分に関しては、見つけることができずにこちらのサイトを見て実装しました。

今回作りたいもの

今回作りたいのは、以下のフォームになります。作成の過程で、「自作コンポーネント」として右側のものを一つのコンポーネントとして切り出して作成をしたいです。デザイン部分は「Tailwind」で作成しています。

完成品・自作コンポーネントの完成品のデザインカンプ

それでは、コードに入っていきましょう。

コード

先にフォームを生成するコンポーネントを記載します。

import { SubmitHandler, useForm } from 'react-hook-form';

import { InputItemComponent } from './InputItemComponent';

export const InputBoardComponent = () => {
  type InputFormPrideContentType = {
    title: string;
    serviceName: string;
    infoName: string;
  };
  const { handleSubmit, control, reset } = useForm<InputFormPrideContentType>({
    defaultValues: {
      title: '',
      serviceName: '',
      infoName: '',
    },
  });

  const onSubmit: SubmitHandler<InputFormPrideContentType> = (data: InputFormPrideContentType) => {
    console.log(data);
    reset();
  };
  return (
    <>
      <form
        onSubmit={handleSubmit(onSubmit)}
        className="flex w-full  max-w-xl flex-col gap-5 rounded p-4 shadow-2xl"
      >
        <InputItemComponent
          name={'title'}
          control={control}
          rules={{
            required: { value: true, message: 'アピールは書いてね♡' },
            maxLength: { value: 20, message: '文字数は20文字以内です。' },
          }}
          label="アピールポイント"
          validation="20文字以内"
        />
        <InputItemComponent
          name={'serviceName'}
          control={control}
          rules={{
            required: { value: true, message: 'これは難しい' },
            maxLength: { value: 20, message: '文字数は10文字以内です。' },
          }}
          label="対象サービス"
          validation="10文字以内"
        />
        <InputItemComponent
          name={'infoName'}
          control={control}
          label="顧客名・社内検証等"
          rules={{
            required: { value: true, message: 'これは埋めときましょう' },
            maxLength: { value: 10, message: '文字数は10文字以内です。' },
          }}
          validation="10文字以内"
        />
        <button
          type={'submit'}
          className="flex items-center justify-center border-2 border-gray py-3 px-8 text-lg"
        >
          投稿
        </button>
      </form>
    </>
  );
};

今回の肝となる部分としては、「InputItemComponent」の部分に「react-hook-form」の情報(name, control, rules)とデザイン面で必要になる情報(label, validation)を一緒に渡しています。

それでは「InputItemComponent」になります。

import { FieldValues, useController, UseControllerProps } from 'react-hook-form';

type LabelProps = {
  label: string;
  validation: string;
};

type InputItemComponentProps<T extends FieldValues> = UseControllerProps<T> & LabelProps;

export const InputItemComponent = <T extends FieldValues>(props: InputItemComponentProps<T>) => {
  const { label, validation, name, control, rules } = props;
  const { field, fieldState } = useController<T>({ name, control, rules });
  const { error } = fieldState;

  return (
    <>
      <div className="flex w-full flex-col">
        <div className="flex flex-row justify-between text-lg text-gray">
          <span>{label}</span>
          <span>{validation}</span>
        </div>
        <input type="text" {...field} className="border border-gray py-1 px-2 text-lg" />
        <span className="h-4 w-full text-xs text-red-600">{error ? error.message : ''}</span>
      </div>
    </>
  );
};

ここで注目してほしい点としては、「ジェネリクスを用いてUseControllerPropsの型を制御している」という点です。今回は、「汎用型」ということを考慮していて、複数のフォームに横断して使えるようにするためにこの方法を採用しています。

もしジェネリクスを用いない場合は、typeをexportしてジェネリクス部分に入れ込むという対応になります。この場合、「InputItemComponent」は定義した型にしか対応することができない、専用コンポーネントとなります。ジェネリクスを用いることで、動的に型制御を行い汎用性を高めています。

フォームの入力のタイミングでエラー(warning)が出る

作成段階で詰まったところを箇条書きで書いておきます。もしフォームを動かしていて、「Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.」というエラーに直面した場合は、フォームのdefaultValueを設定していないことによるエラーだと思います。フォームの初期値は設定しておきましょう。

終わりに

ども!今日は直近で最も脳汁が出た内容について書いてみました。ジェネリクスを使うという結論に至るまで、なかなか時間を費やしたので誰かの助けになればと思います。おそらくですが、このコードは自分が一番使うことになるだろうなと思っていますw。

そういった意味でも、備忘録的にブログを出すことは大切だなと思っています。最近は、Figmaを布教しようとFigma関連の記事を一生懸命書いています。お時間あるときにご一読ください。

今日はいっぱいブログを書いたので、そろそろコードを書こうかなと思います!それではまた!!

アバター画像
About 龍:Ryu 107 Articles
2022年入社で主にフロントエンドの業務でTailwindと遊ぶ日々。お酒とうまいご飯が好きで、運動がちょっと嫌いなエンジニアです。しゃべれるエンジニアを目指しておしゃべりとブログ執筆に注力中(業務もね)//
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

1人がこの投稿は役に立ったと言っています。


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる