LoginSignup
0
0

【React(TypeScript)】react-hook-formを使ったカスタムテキストボックスを作る

Last updated at Posted at 2023-12-16

概要

テキストボックスを拡張したコンポーネントをreact-hook-formで使えるようにするには一工夫必要です。registerをどうやって渡したらいいのか分からないという方も多いのではないでしょうか。試行錯誤してreact-hook-formに対応したカスタムテキストボックスを作ることができましたのでマイベストプラクティスとして共有させていただきたいと思います。

やり方

ジェネリクスを駆使してreact-hook-formに必要なパラメータを渡すようにします。今回はテキストボックスを作っていますが、ラジオボタンなど他のinputでも基本的な作りは変わらないと思います。

※以下ではuse-mask-inputを用いたマスク機能を持ち、バリデーションエラーメッセージを表示できるカスタムテキストボックスを作ります。CSSフレームワークとしてtailwind CSSを使っていますので、別のCSSフレームワークを使っている場合はCSSの箇所を変更してください。

TextBox.tsx

registerやerrorsなどreact-hook-formに必要なパラメータをPropsに定義します。型が難しいので真似て作ってください。

import { type HTMLInputTypeAttribute } from 'react'
import {
  type UseFormRegister,
  type FieldValues,
  type FieldErrors,
  type Path,
} from 'react-hook-form'

import { useHookFormMask } from 'use-mask-input'

type InputProps = React.ComponentProps<'input'>

type Props<T extends FieldValues> = InputProps & {
  register: UseFormRegister<T>
  errors: FieldErrors<T>
  fieldName: Path<T> // instead of a string
  type?: HTMLInputTypeAttribute
  mask?: string // https://www.npmjs.com/package/use-mask-input
  maskOption?: object
}

const TextBox = <T extends FieldValues>({
  register,
  errors,
  fieldName,
  type = 'text',
  mask,
  maskOption,
  id,
  ...others
}: Props<T>) => {
  const registerWithMask = useHookFormMask(register)

  function createInput() {
    const defaultClassName =
      'mt-2 block w-full rounded-md border bg-white px-4 py-3 focus:border-blue-400 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-40 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:focus:border-blue-300'

    if (mask == null) {
      return (
        <input
          type={type}
          id={id ?? fieldName}
          className={`${defaultClassName} ${getBorderColor()}`}
          {...register(fieldName)}
          {...others}
        />
      )
    } else {
      return (
        <input
          type={type}
          id={id ?? fieldName}
          className={`${defaultClassName} ${getBorderColor()}`}
          {...registerWithMask(fieldName, mask, maskOption)}
          {...others}
        />
      )
    }
  }

  function getBorderColor() {
    if (errors[fieldName] != null) {
      return 'border-error'
    }
    return 'border-gray-200'
  }

  return (
    <>
      {createInput()}
      {errors[fieldName]?.message != null && (
        <p className="text-error">{String(errors[fieldName]?.message)}</p>
      )}
    </>
  )
}

export default TextBox

使い方

registerやerrorsなどのパラメータを渡します。バリデーションのライブラリとしてはyupを使用していますが、他のライブラリでも動くと思います。

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

import { yupResolver } from '@hookform/resolvers/yup'
import TextBox from 'components/TextBox'
import * as yup from 'yup'

type FormValues = {
  title: string
}

const informationSchema = yup.object().shape({
  title: yup.string().required().max(30),
})

export default function FormWithValidation() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    resolver: yupResolver(informationSchema),
  })

  const onSubmit: SubmitHandler<FormValues> = (data) => {
    console.log('submitData', data)
  }

  return (
    <div>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <TextBox register={register} errors={errors} fieldName="title" />
        </div>
        <div>
          <button type="submit">送信</button>
        </div>
      </form>
    </div>
  )
}
0
0
0

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
0
0