Closed8

TanStack FormでServer Actionsを試す

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

ドキュメントの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;
kmkkiiikmkkiii

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で実装が進められている
https://github.com/TanStack/form/pull/656

kmkkiiikmkkiii

useFormState

https://ja.react.dev/reference/react-dom/hooks/useFormState

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