はじめに
Webアプリを開発する際は, 入力フォームとそのバリデーションを実装することが非常に多いと思います. 皆さんも必要なプロパティが存在するか, 値が適切な範囲に収まっているか, 目的の型にキャストできるかなどをチェックするコードをたくさん書いてきたのではないでしょうか?🤔
一般的にバリデーションは, クライアントサイドでのバリデーションとサーバーサイドでのバリデーションとで別々に実装する必要があるため, バリデーションルールを表現するコードがDRYにならないという問題があります. しかも二度手間で面倒です😱
こういった煩雑さから解放させてくれるのが, 今回紹介するremix-validated-formというライブラリです✨
remix-validated-formは, 最近流行りのフルスタックフレームワークであるRemixと組み合わせて利用するライブラリであり, クライアントサイドとサーバーサイドでのバリデーションを一本化してくれる優れものです🔥
zodやyupといった有名なバリデーションライブラリと連携して利用することも可能です. zodを用いる場合は以下のコマンドでインストールできます.
> npm install remix-validated-form @remix-validated-form/with-zod
使い方
今回は以下のような「新規ユーザーの登録」を例として, remix-validated-formの使い方を紹介していきます. (バリデーションにはzodを使います)

バリデーターの作成とフォームの実装
remix-validated-formを使うには, まずバリデーターを作成する必要があります.
import { withZod } from "@remix-validated-form/with-zod";
import { z } from "zod";
export const userRegisterValidator = withZod(
z.object({
age: z.coerce
.number({ invalid_type_error: "年齢は数字で入力してください" })
.safe(),
email: z
.string()
.email({ message: "メールアドレスを正しい形式で入力してください" }),
name: z.string().min(1, { message: "名前が空になっています" }),
})
);
次に, フォームを実装したいページでValidatedFormコンポーネントを利用します.
このコンポーネントはvalidatorが必須Propになっており, フォームに適用したいバリデーターを渡します.
これだけで, フォームに入力中のデータに対してPropsで渡したバリデーターによるクライアントサイドでのバリデーションが実装完了です✨
import { Button, Text, VStack } from "@chakra-ui/react"; // ← スタイリングにChakra UIを使っています
import { ValidatedForm } from "remix-validated-form";
import { userRegisterValidator } from "./userRegisterValidator";
...
const RegisterPage = () => {
return (
<>
...
<Text fontSize="lg" fontWeight="bold" textDecoration="underline">
ユーザ登録
</Text>
<ValidatedForm
validator={userRegisterValidator} // ← バリデーターを設定(必須)
action="/register"
method="POST"
>
<VStack>
<InputFields /> {/* ← 入力欄をまとめたコンポーネント(ユーザー定義) */}
<Button type="submit" colorScheme="purple">
登録する
</Button>
</VStack>
</ValidatedForm>
</VStack>
</>
);
};
export default RegisterPage;
フォームから送信されたデータに対するサーバーサイドでのバリデーションは, validator.validate()メソッドを実行するだけで簡単に行うことができます.
import { validationError } from 'remix-validated-form'
...
export const action = async ({ request }: ActionArgs) => {
const formData = await userRegisterValidator.validate(
await request.formData()
);
if (formData.error) {
console.log(formData.error);
return validationError(formData.error);
}
const { name, age, email } = formData.data; // ← zodでバリデーション済みのデータを簡単に取得できます
return json({ name, age, email });
};
(※) register.tsx内のaction関数はこのエンドポイント(=/register)にリクエストが送信されたときにサーバーサイドで実行される関数です.
バリデーションエラーの取得方法
サーバーサイドでのバリデーションエラーは, formData.errorで簡単に取得できます.
また, validationError関数をformData.errorに使うことで簡単にエラーレスポンスを生成することができます.
一方で, クライアントサイドのバリデーションエラーは, remix-validated-formで定義されているuseFieldというフックを利用することで取得することができます.
例として, useFieldによるエラーの取得とアラートの表示は以下のように実装できます.
import { Alert, AlertIcon, HStack, Input,vText } from "@chakra-ui/react";
import { useField } from "remix-validated-form";
export const InputFields = () => {
const nameError = useField("name");
...
return (
...
<HStack>
<Text mr={4}>名前 :</Text>
<Input
name="name"
width="15vw"
backgroundColor="white"
border="1px solid black"
/>
</HStack>
{nameError.error && ( // ← エラーがある場合は, アラートコンポーネントを表示する
<Alert status="error" width="20vw">
<AlertIcon />
{nameError.error}
</Alert>
)}
...
);
};
バリデーション実行例
クライアントサイド
実際にバリデーションエラーが起きる例として, メールアドレスに不適切な値を入力してみました.
その結果, ブラウザ上では下の画像のように適切なエラーメッセージが表示されました.

一方で, ターミナルでRemixサーバの出力を見てもフォーム送信のログが出力されていないことから, クライアントサイドでのバリデーションが実行され, 通過しなかったためにリクエストが送信されていないことが確認できます✨
)
※ちなみに, バリデーションをパスしたデータがフォームによって送信された場合は以下のような出力になります.

サーバーサイド
サーバーサイドでのバリデーションを確認するため, 先ほどと同じエラーが起きるデータをcurlを使って送信しました.

同様にターミナルでRemixサーバの出力を確認すると, クライアントサイドで表示されたエラーメッセージと同じエラーが出力されていることがわかります.
レスポンスのステータスコードは422なのですが, このステータスコードのレスポンスメッセージはUnprocessable Entityなので, 送信された内容の理解はできるものの正しく処理できなかったことを表しています!!

まとめ
バリデーターを一つ定義するだけでクライアントサイドとサーバーサイドの両方で一貫したバリデーションを行うことがremix-validated-formを使えば簡単に実現できます.
また, zodやyupといった有名なバリデーションライブラリとの連携を想定して作られているので, 利用経験のあるバリデーションライブラリをそのまま使うことができます.
さらに, クライアントサイドではValidatedFormにバリデーターをPropsとして渡すだけでフォームにバリデーションを設定でき, useFieldを使って直感的に対象のフィールドのエラーを取得できます. サーバーサイドではvalidator.validate()を実行するだけでバリデーションを実行できるうえに, validationError関数をformData.errorに使うことで簡単にエラーレスポンスを生成できます. こういった諸々の操作が簡単であることも魅力に感じています.
Remixを使ってWebアプリを開発している方やRemixに興味があってこれから触ってみたい方に是非おすすめしたいライブラリです!!
何か不明な点などがあれば, 遠慮なくコメントしてください😊