🎲

shadcnとzodを使って電話番号のフォームを作る

2024/04/22に公開

shadcn + react-hook-form + zodを使用して電話番号を入力するフォームを実装した時のメモ

環境

  • NextJS: 14.1.0
  • zod: ^3.22.4
  • react-phone-number-input: ^3.3.12
  • react-hook-form: ^7.49.2

react-phone-number-input

このライブラリを使えば大体解決になる。

内部的にはlibphonenumber-jsという軽量なライブラリを使っている

libphonenumber-jsはGoogleが提供しているgoogle/libphonumberを拡張したもの

install

pnpm add react-phone-number-input

usage

react-phone-number-inputは様々なPhoneInputコンポーネントを提供している

インポートパスには注意が必要

shadcn/react-hook-formと使う

shadcnと使うならFormLabelやFormMessageと組み合わせたいのでFormField内にまとめておいてしまうと良い

自分が使った時は日本の電話番号に限定して良かったので、country=”JP”で固定しておく

import PhoneInput from "react-phone-number-input/react-hook-form-input";

const PhoneInputFormField = () => {
  const { control } = useFormContext();
  return (
    <FormField
      control={control}
      name="phoneInput"
      render={({ field: {onChange, value} }) => (
        <FormItem className={cn("flex flex-col gap-1", className)}>
          <div className="inline-flex items-center gap-2">
            <FormLabel>phone number</FormLabel>
          </div>
          <div className="inline-flex w-full items-center gap-5">
            <FormControl>
              <PhoneInput
                country="JP"
                required={false}
                className="h-full rounded-md border-none bg-gray-100 px-3 py-4"
                placeholder"090-1234-5678"
                onChange={onChange}
                defaultValue={value}
              />
            </FormControl>
          </div>
          <FormMessage />
        </FormItem>
      )}
    />
  );
};

https://github.com/catamphetamine/react-phone-number-input/issues/405

※このIssueはreact-phone-number-input/react-hook-form-inputのPhoneInputを使ってそうなったというバグを報告している(既に修正済み)が、react-phone-number-input/inputのPhoneInputを使うと同じようになる

国を入力する

公式ドキュメントにあるように、countryをユーザーに入力させるPhoneInputWithCountryコンポーネントも別で提供されている

import 'react-phone-number-input/style.css'
import PhoneInput from 'react-phone-number-input'

function Example() {
  // `value` will be the parsed phone number in E.164 format.
  // Example: "+12133734253".
  const [value, setValue] = useState()
  return (
    <form onSubmit={handleSubmit(...)}>
      <PhoneInputWithCountry
        name="phoneInputWithCountrySelect"
        control={control}
        rules={{ required: true }} />
      <button type="submit">
        Submit
      </button>
    </form>
  )
}

utils

react-phone-number-inputにはlibphonenumber-js由来のユーティリティ関数がExportされている

フォームで入力した値をローカルな形式に変換する

formatPhoneNumberを使うと良い

import { formatPhoneNumber } from "react-phone-number-input";

const { getValues } = useFormContext()
const values = getValues()
console.log(values.phoneInput) // "+819012345678"
const phone = formatPhoneNumber(values.phoneInput) // 自動でローカライズされる
console.log(phone) // 090-1234-5678

Zodで電話番号のバリデーションをする

isValidPhoneNumberは長さや形式が電話番号であるかを判定する

長さだけを判定するisPossiblePhoneNumberも必要に応じて使えるだろう

import { isValidPhoneNumber } from "libphonenumber-js";
import { z } from "zod";

export const SampleSchema = z.object({
  PhoneInput: z
    .string({
      required_error: "電話番号を入力してください",
      invalid_type_error: "電話番号を入力してください",
    })
    .refine(isValidPhoneNumber, { message: "電話番号が正しくありません" })
  });

reference

https://gitlab.com/catamphetamine/react-phone-number-input

https://gitlab.com/catamphetamine/libphonenumber-js

Discussion