Closed8
TanStack FormでServer Actionsを試す

Next.jsでServer Actionsを使ってみるにあたり、対応したフォームライブラリを探していた。
現状RHFは未対応、conformは最近記事を見かけたので、TanStack Form + Zod + Server Actionsでフォーム作成をやってみる。

RHFのServer Actionsサポートの動向

TanStack Formのドキュメント

- TanStack FormはClient Componentで使用する必要がある
- 2パターンの使用方法がある
- useFormフック
- createFormFactory
- formインスタンスを作成
- formインスタンスはformを操作するメソッドやプロパティを提供する
- formフィールドのデフォルト値を含むオブジェクトを受け取る
- formインスタンスを作成

ドキュメントのzod exampleみたいなfieldごとにzod validation定義するのではなく、zod schemaでform全体にバリテーションを適用したい。
useFormのvalidatorsでonChangeイベントにschema指定したら動いた。
イベントに対して設定できるのでonSubmitにschema指定するとよさそう。
あとは各inputの下にエラーメッセージ表示されるようにして、Server Actionsと組み合わせて使ってみる。
"use client";
import { useForm } from "@tanstack/react-form";
import { zodValidator } from "@tanstack/zod-form-adapter";
import { z } from "zod";
const schema = z.object({
name: z.string().min(2, "Name must be at least 2 characters long"),
email: z.string().email("Invalid email address"),
});
const Form = () => {
const { useStore, Subscribe, handleSubmit, Field } = useForm({
defaultValues: { name: "", email: "" },
validatorAdapter: zodValidator,
validators: { onChange: schema },
onSubmit: async ({ value }) => {
// Do something with form data
console.log(value);
},
});
const formErrors = useStore((formState) => formState.errors);
return (
<>
<form onSubmit={() => handleSubmit()}>
{formErrors.map((error) => (
<p className="text-red" key={error as string}>
{error}
</p>
))}
<Field name="name">
{(field) => (
<div>
<input
className="text-black border border-gray p-2"
name="name"
type="text"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
</Field>
<Field name="email">
{(field) => (
<div>
<input
className="text-black border border-gray p-2"
name="email"
type="email"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
</Field>
<Subscribe
selector={(formState) => [
formState.canSubmit,
formState.isSubmitting,
]}
>
{([canSubmit, isSubmitting]) => (
<button
type="submit"
disabled={!canSubmit}
className="text-white bg-blue-700 hover:bg-blue-800 p-2 mt-2 rounded-lg"
>
{isSubmitting ? "..." : "Submit"}
</button>
)}
</Subscribe>
</form>
</>
);
};
export default Form;

discordを覗いてみたら、現状はformレベルでバリデーションを行う場合、どのfieldがエラーとなったか知る方法はないみたい。近々修正されるとのこと。
Leonardo — 2024/03/30 07:05
Question: when validating at form level (passing a valibot adapter) is there a way to know which field is erroring? form.state.errors only shows a string array with all the errors
crutchcorn — 2024/03/30 08:36
Not today, but @fuko is fixing this soon
以下のPRで実装が進められている

useFormState
- Reactでは現時点でcanary、experimentalチャンネルのみで利用できる新しいフック
- フォームのアクション結果に基づいて状態を動的に更新できるようにする
- このactionをformのactionに利用することで、フォーム送信時にServer Actionsがトリガーされる
const [state, action] = useFormState(someAction, initialFormState)
- Server Actionsでサーバー側のバリデーションを行い、クライアント側のバリデーションはhandleSubmitで行う

記事書いた
このスクラップは2024/04/15にクローズされました