見出し画像

React Hook FormとZodの配列管理

CyberZのWebフロントチームでエンジニアをしている山﨑です。CyberZでは、新たな技術の導入を積極的に行なっており、その中で React Hook Form と zodを使用したフォームの実装を行なったため、その実装例をご紹介したいと思います。

前回の記事について

前回の記事では、ユーザーのサインイン・サインアップなど、シンプルな文字列の操作のみを必要とするフォームの実装例を紹介しました。しかし、フォームとしてユーザーの入力項目や設定が複雑になった場合、React Hook Formやzodの実装で工夫が必要になる箇所が出てきます。その一つが、配列の扱いについてです。「任意項目を複数回選択・入力できる」といった、ユーザー毎に異なる挙動を必要とする場合に、フォームの中で配列の管理が必要になってきます。
しかし、前回の記事で紹介したシンプルなuseFormとzodの管理だけでは実装が難しかったため、今回の記事ではより詳細な値の管理について紹介したいと思います。

配列の空許容の選択フォームの実装

React Hook Formでシンプルな配列(文字列・数値・真偽値)を管理する場合、前回の記事で紹介したuseForm APIを使用して実装することができます。以下の例では、チェックボックスを作成して、その値を文字列の配列として管理してます。
前回の記事と異なるポイントとしては、useForm APIに対して、defaultValuesを指定しているところです。useFormでは、defaultValuesが指定されていない状態でフォームの検証を行った時、データをfalseとして扱います。そのため、何も値が選択されていない状態を許容する場合は、defaultValuesを指定してあげる必要があります。

// フォームの型定義
type Inputs = {
    array: string[];
}

const Form = () => {
  const {
    handleSubmit,
    register,
  } = useForm<Inputs>({
    defaultValues: {
	  array: [],   // 指定がないと { array: false } になるよ
    },
  });

  const onSubmit = useCallback((data: Form) => {
    console.log(data)
  }, []);

  return (
    <form onSubmit={ handleSubmit(onSubmit) }>
	  <input
        { ...register('array') }
        type="checkbox"
        value="ValueA"
      />
      <input
        { ...register('array') }
        type="checkbox"
        value="ValueB"
      />
      <input
        { ...register('array') }
        type="checkbox"
        value="ValueC"
      />

       <button type='submit'>保存</button>
    </form>
  )
}

配列の空許容でない選択フォームの実装

また、空配列を許容せず、最低でも1つ以上の選択を必要とする場合には、Zodのスキーマに対してnonemptyを指定することで実現することができます。

// フォームのスキーマ
const InputsSchema = z.object({
	 array: z.string().array().nonempty('1つ以上選択してください'),
});

// フォームの型定義
type Inputs = z.infer<typeof FormSchema>;

const Form = () => {
    const {
    register,
    handleSubmit,
	formState: { errors },
  } = useForm<Inputs>({
    resolver: zodResolver(InputsSchema),
    defaultValues: {
      array: [],
    },
  });

    const onSubmit = useCallback((data: Form) => {
        console.log(data)
    }, []);

    return (
        <form onSubmit={ handleSubmit(onSubmit) }>
	    <input
          { ...register('array') }
          type="checkbox"
          value="ValueA"
        />
        <input
          { ...register('array') }
          type="checkbox"
          value="ValueB"
        />
        <input
          { ...register('array') }
          type="checkbox"
          value="ValueC"
        />

	    { errors.array && <span>array error</span> }
        <button type='submit'>保存</button>
        </form>
	)
}

配列の空許容の入力フォームの実装

React Hook Formで動的に変化する文字列を配列で管理するためには、React Hook Formで提供されているuseFieldArray APIを、useFormと掛け合わせて実装する必要があります。

まずは、useFieldArrayで配列を管理する上で必要な最低限の返り値を紹介します。

  • fields

この配列は、実際に管理している配列の値になります。初期値は空の配列になっているため、最初から値を入れておきたい場合は、useFormのdefaultValuesで追加することができます。

  • append

この関数は、新たに管理する配列を追加する関数になります。追加したい値を引数で渡すことができます。

  • remove

この関数は、管理している配列を削除する関数になります。削除したい値のindexで指定してあげることで、配列から特定の値を削除することができます。

次に、useFieldArrayと掛け合わせる上で必要なuseFormの値を紹介します。

  • control

このオブジェクトは、useFormで値を管理するためのメソッドが含まれた値になります。このオブジェクトをuseFieldArrayに渡してあげることで、useFormで管理している値を参照し、掛け合わせることができます。

  • register

このメソッドは、HTMLのinputタグもしくはselectタグに対して使用することができ、その要素が検証のルールに則っているか判定することができます。

これらを使って簡単に実装例を紹介すると、以下のようになります。

// フォームの型定義
type Inputs = {
  field: {
        id: string;
	content: z.string();
    }[],
}

function FieldArray() {
    const {
        control,                  // useFormとuseFieldArrayとの整合性を保ってくれる
	register,              // どの配列に対しての処理か指定してあげる
    } = useForm<Inputs>();

  const {
	fields, // 実際に管理している配列の値
	append, // 配列に値を追加する関数
	remove, // 配列の値を削除する関数
    } = useFieldArray({ control, name: 'field' }); // nameは配列として管理するkey

    const array = fields.map((field, index) => (
        <>
	    <input
	        key={field.id}
		{...register(`test.${index}.value`)} 
	    />
	    <button onClick={ () => remove(index) }>削除</button>
	</>
  ));

  return (
        <>
	    { array }

	    { errors.email && <span>email error</span> }
	    <button onClick={ () => append({}) }>追加</button>	
        </>
  );
}

ここで注意しなければならないポイントとしては、useFieldArrayのnameで指定してあげる値は、配列として管理しているオブジェクトのkeyである必要があります。

配列の空許容でない入力フォームの実装

最後に、空配列を許容しない時の入力フォームの実装を紹介します。ここでは、配列が空かどうかの判定だけなく、ユーザーが入力した内容に対しての検証も行うことで、より安全性の高いフォームを実装することができます。

const TextFieldSchema = z.object({
  id: z.string(),
    content: z.string(),
});

// フォームのスキーマ
const FormSchema = z.object({
  field: TextFieldSchema.array().nonempty('1つ以上選択してください'),
});

// フォームの型定義
type Inputs = z.infer<typeof FormSchema>;

function FieldArray() {
    const {
        control,      // useFormとuseFieldArrayとの整合性を保ってくれる
	    register,  // どの配列に対しての処理か指定してあげる
	    formState: { errors }, // 検証のエラーを管理してくれる
        } = useForm<Inputs>({
	    resolver: zodResolver(FormSchema),
	});

    const {
        fields, // 実際に管理している配列の値
	append, // 配列に値を追加する関数
	remove, // 配列の値を削除する関数
    } = useFieldArray({ control, name: 'field' }); // nameは配列として管理するkey

    const array = fields.map((field, index) => (
        <>
	    <input
	        key={field.id}
		{...register(`test.${index}.value`)} 
	    />
	    <button onClick={ () => remove(index) }>削除</button>
	    { errors.field?.[index].content && <span>${ index } error</span> }
        </>
  ));

  return (
        <>
	    { array }
	    <button onClick={ () => append({}) }>追加</button>	
        </>
  );
}


フォームの実装例

※ スタイルは適宜当てています

まとめ

今回の記事では、React Hook Formとzodを利用した配列の管理方法を紹介しました。今回の記事を通して、React Hook Formとzodのそれぞれのできること、できないことの特徴を把握できたかなと感じています。前回の記事も含め、これらのライブラリの有用性を改めて感じたため、今後も積極的に活用していきたいと思います。

この記事が気に入ったらサポートをしてみませんか?