🏠

【React】軽量なFormライブラリ「HouseForm」を試してみた

2023/08/19に公開

概要

ReactでのFormのライブラリとなると、2023年現在ではReact Hook FormFormikが使われることが多いかと思います。私はReact Hook Formを使用することが比較的あるのですが、機能が複雑な部分は感じておりドキュメントを読み込まないと、使いこなすのは少し大変な印象はあります。
もちろん大きめの開発ならReact Hook Formを採用することは全然妥当と思いますが、少し小さめの開発でライトに使えそうなFormのライブラリはあるかと探してみました。今回はHouseFormというライブラリを少し試してみたので紹介します。

HouseFormについて

2023年にv1がリリースされたライブラリです。Comparison Against Other Form Librariesに他ライブラリとの比較がまとめられています。かなりざっくりまとめると、

  • 軽量なライブラリである。
  • バリデーションにzodを使える。
  • インプット項目に対して柔軟に制御ができる。

といった点が特徴かなと感じました。

実装サンプル

取り急ぎ、以下のような画面を実装した時のサンプルです。

使用したHouseFormのバージョンは1.11.0です。なお、UIのライブラリについては今回flowbite-reactTailwind Variantsといったのを使用していますが、他のものでも問題なく使用できると思います。

import React, { FC } from "react";

import { Button, Label, TextInput } from "flowbite-react";
import { Field, Form } from "houseform";
import { z } from "zod";
import {
  formBlockStyle,
  inputLabelStyle,
  inputTextStyle,
} from "@/style/FormStyle";
import { errorMessageStyle } from "@/style/MessageStyle";
import { centerHorizonContainerStyle } from "@/style/CommonStyle";

type UserRegisterForm = {
  userId: string;
  name: string;
};

export const UserRegisterComponent: FC = () => {
  return (
    <Form<UserRegisterForm>
      onSubmit={(values) => {
        console.log(values);
      }}
    >
      {({ submit }) => (
        <div>
          <Field<string>
            name="userId"
            onSubmitValidate={z.string().nonempty("ユーザIDの入力は必須です")}
          >
            {({ value, setValue, errors }) => (
              <div className={formBlockStyle()}>
                <Label
                  htmlFor="userId"
                  value="ユーザID"
                  className={inputLabelStyle({ type: "required" })}
                />
                <TextInput
                  id="userId"
                  value={value}
                  onChange={(e) => setValue(e.target.value)}
                  className={inputTextStyle()}
                />
                {errors.map((error) => (
                  <p key={error} className={errorMessageStyle()}>
                    {error}
                  </p>
                ))}
              </div>
            )}
          </Field>
          <Field<string>
            name="name"
            onSubmitValidate={z.string().nonempty("名前の入力は必須です")}
          >
            {({ value, setValue, errors }) => (
              <div className={formBlockStyle()}>
                <Label
                  htmlFor="name"
                  value="名前"
                  className={inputLabelStyle({ type: "required" })}
                />
                <TextInput
                  id="name"
                  value={value}
                  onChange={(e) => setValue(e.target.value)}
                  className={inputTextStyle()}
                />
                {errors.map((error) => (
                  <p key={error} className={errorMessageStyle()}>
                    {error}
                  </p>
                ))}
              </div>
            )}
          </Field>
          <div className={`${centerHorizonContainerStyle()} mt-4`}>
            <Button
              color="success"
              pill
              onClick={() => {
                submit();
              }}
            >
              <p>登録</p>
            </Button>
          </div>
        </div>
      )}
    </Form>
  );
};

所感など

  • インプット要素で使用するHouseFormの機能は主にvalue, setValue, errorsになる感じなので、HouseFormに引っ張られずUIをけっこう柔軟に作れる感じがしました。
  • バリデーションのタイミングを、フィールド毎に制御できるのが良い感じがします。onSubmitValidateonBlurValidateなども設定できます。
  • ジェネリクスを使用した型指定がけっこう良さげで、型指定すればよしなに判断してくれる感じです。
  • 今回試してませんが、Nested Field ValuesForm Arraysも、そこまで苦労なく実装できそうなイメージです。

Discussion