✅
Remix Validated Form で POST action 正常終了で完了画面の TypeScript 型判定は型ガード必要
結論
こんな感じの型ガードをフォームごとに作るのが一番マシ
export const isSuccessResult = (result: unknown): result is { success: true } =>
!!result &&
typeof result === 'object' &&
'success' in result &&
result.success === true
こんにちは。最近また推しである Remix 再評価の雰囲気が出てきて嬉しい coji です。
Remix Validated Form 便利ですよね。
その便利なのを使って、問い合わせフォーム作ってて、登録が完了したときに「ありがとうございました」って出したいとか、よくあります。
だけど、サーバ側でバリデーションチェックをしつつ、正常終了を useActionData でとってきて確認して、完了画面を出すをやりたかったのですが、こうなっちゃうんですよ。
再現コードはこちら。
contact.tsx
import { type ActionArgs, json } from '@remix-run/node'
import { useActionData } from '@remix-run/react'
import { ValidatedForm } from 'remix-validated-form'
import { withZod } from '@remix-validated-form/with-zod'
import { z } from 'zod'
// サーバ・クライアント共通で使う validator
const validator = withZod(z.object({
name: z.string().min(1, '必須').max(50, '50文字以内です'),
email: z.string().min(1, { message: '必須' }).max(100, '100文字以内です').email('メールアドレスでなければなりません'),
message: z.string().min(1, { message: '必須' }).max(10000, '10000文字以内です'),
}))
// action
export const action = async ({request}: ActionArgs) => {
// サーバ側でのバリデーションチェック
const result = await validator.validate(await request.formData())
if (result.error) {
return validationError(result.error) // エラー
}
// メール送ったりなんなりする
// 成功
return json({success: true})
}
// フォーム
export default function ContactFormPage() {
const actionData = useActionData<typeof action>()
// action が成功してたら完了表示にしたい
if (actionData?.data.success) { // <==!!!
return <div>お問い合わせありがとうございました。追ってご連絡しますのでおまちください。</div>
}
return (
<ValidatedForm method="POST" validator={validator}>
<label htmlFor="name">Name</label>
<input id="name" name="name" />
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" />
<label htmlFor="name">Message</label>
<textarea id="message" name="message" />
<button type="submit">submit</button>
</ValidatedForm>
)
}
まあ action の中で validationError も返してるから、型を絞れてないってことなんですよね。
それで、仕方なく以下のようにしたりしますが、もうこれすっごくやだな〜ともってたんですよ。
// action が成功してたら完了表示にしたい
if (typeof actionData === 'object' && 'success' in actionData && actionData.success === true) { // <==!!!
return <div>お問い合わせありがとうございました。追ってご連絡しますのでおまちください。</div>
}
で、Remix Validated Form のイシューみたら、Author の方がこんな回答してるのを見つけましてね。
Your action return both validationError & json('...'), which means you will have to use type guard in order to get the type works.
ああ、やっぱりそうなのか〜。と悲しみにくれたという落ちでした。
ずっとなんとかならないかと思ってたんですが、まあ、author の方がそうおっしゃるなら、まあそれが一番よいのだろうなあ。他に思いつかないし。ということだけを記録したかったので記事にしたのでした。
まあこれはちょっと残念ではあるんですが、全体的にそういうところも含めて Remix は推せるので、推していきます。
Discussion
この問題に辿り着いてこの記事を見ました。解決策は無理矢理感あってちょっと残念でしたが、逆に言えばガードするだけなので良しです笑