ZodとConformを使ってRemixでバリデーションを実施する
はじめに
個人で開発しているプロダクトにおいて、入力フォームを設置しています。 この入力フォームのバリデーションには、ZodとConformを利用しています。 Remixでバリデーションを行う際には、サーバーサイドとクライアントサイド両方で実施することができます。 この実装方法について、記載していきます。
実装方法
入力バリデーション自体はConformを使いますが、その基本となるSchema定義にはZodを利用しています。 そのため、まずはZodによるSchema定義の方法をお伝えします。 その上で、Conformによるクライアントサイドならびにサーバーサイドそれぞれでのバリデーションの実装方法を記載します。
ZodによるSchema定義
今回の入力フォームはキーワード(keyword)とそれに対する説明(description)を入力するフォームとなります。 この2つの入力項目に対するSchemaは以下のように定義できます。
import { z } from 'zod'
export const WordSchema = z.object({
keyword: z
.string()
.min(1, 'a new word is required.')
.max(256, 'a new word is too long.'),
description: z
.string()
.min(1, 'a description is required.')
.max(2024, 'a description word is too long.')
})
それぞれの入力項目に対して、型と入力する文字列の長さの最小値と最大値を指定しています。
また、max(256, 'a new word is too long.')
の第2引数ように、バリデーションに違反した際のメッセージも合わせて記載しています。
クライアントサイドのバリデーション
入力フォームとクライアントサイドのバリデーションに関わるコードのみ抜粋すると以下のようになります。
import { Form } from '@remix-run/react'
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { parseWithZod } from '@conform-to/zod'
import { WordSchema } from '~/schema'
import FormError from '~/components/formError'
export default function Index() {
...
const [form, fields] = useForm({
shouldValidate: 'onBlur',
shouldRevalidate: 'onBlur',
onValidate({ formData }) {
return parseWithZod(formData, { schema: WordSchema })
}
})
return (
...
<Form className="..." method="post" {...getFormProps(form)}>
<label className="...">
<div className="...">
<p className="...">word</p>
<p className="...">Required</p>
</div>
<input
{...getInputProps(fields.keyword, { type: 'text' })}
className="..."
id="keyword"
name="keyword"
type="text"
placeholder="e.g. Keyword"
/>
<FormError errors={fields.keyword.errors} />
</label>
// descriptionにおける入力フォームのUIも同様
</Form>
...
)
}
まずは、useFrom()
によってフォーム要素(form)や入力項目(fields)を取得しています。
shouldValidate
やshouldRevalidate
は、入力においてどのタイミングでバリデーションを実施するかというものを指定しています。
また、onValidate()
にて入力フォームの各値(formData)が呼び出されます。
この呼び出されるformDataと事前に定義したSchema(WordSchema)をparseWithZod(formData, { schema: WordSchema })
のように渡すことで、バリデーションを実施することができます。
一方、UIコンポーネントについてです。
<From>
タグにある{...getFromProps(form)}
ですが、これはフォーム要素に対してアクセス可能にするために必要なpropsを取得するための関数になります。
また、{...getInputProps(fields.keyword, { type: 'text' })}
は、インプット要素に対してアクセス可能にするために必要なpropsを取得するための関数となります。
サーバーサイドのバリデーション
サーバーサイドのバリデーションは以下のコードのように実装できます。
import { ActionFunction } from '@remix-run/cloudflare'
import { parseWithZod } from '@conform-to/zod'
import { WordSchema } from '~/schema'
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData()
const submission = parseWithZod(formData, { schema: WordSchema })
if (submission.status !== 'success') {
return submission.reply()
}
console.log(submission.value)
return submission.value
}
export default function Index() {
...
}
Remixでは、export const action: ActionFunction = async () => {}
によって、サーバーサイドでの動きを規定することができます。
バリデーション自体は、クライアントサイドと同様に、parseWithZod()
にて実現しています。
今回の例では、単純にバリデーションを行うだけとなっていますが、APIへのリクエストなども合わせて記載するのが実際のユースケースになると思います。
さいごに
いかがでしたでしょうか。自身の備忘のためのポストとなっておりますので、わかりにくい部分もあるかと思います。 誰かの役に立てれば、幸いです。