🦁

ReactAriaの<Form>コンポーネントとuseActionStateを使う例

2025/01/24に公開

最近はReactAriaComponentを活用している@zaruです、こんにちは。

Next.js v15(React19)で、ReactAriaの <Form> コンポーネントと useActionState を使っていると、Next.js v15からはフォーム内容がリセットされるの影響で期待した動作にならないことがあります。具体的には useActionState を使って入力値を defaultValue で保持しても、リセットされてしまう現象です。

この記事では、その対処法をメモしておきます。ステートのデータ構造は例なので参考程度に。

"use client";

import { changeName } from "@/app/action";
import { type FormEvent, startTransition, useActionState } from "react";
import {
  Button,
  FieldError,
  Form,
  Input,
  Label,
  TextField,
} from "react-aria-components";

export default function Home() {
  const [state, formAction] = useActionState(changeName, {
    payload: { username: "zaru" },
  });
  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    startTransition(() => formAction(formData));
  };
  return (
    <Form validationErrors={state.errors} onSubmit={handleSubmit}>
      <TextField name="username" defaultValue={state.payload.username}>
        <Label>Username</Label>
        <Input />
        <FieldError />
      </TextField>
      <Button type="submit">Submit</Button>
    </Form>
  );
}
"use server";

export interface Payload {
  username: string;
}
export interface ResponseType {
  payload: Payload;
  errors?: Partial<Record<keyof Payload, string>>;
}

export async function changeName(
  prevState: ResponseType,
  formData: FormData,
): Promise<ResponseType> {
  const username = formData.get("username") as string;
  return {
    payload: { username },
    errors: {
      username: username.length > 5 ? "長すぎます" : "",
    },
  };
}

ポイントは以下の2点です。

  • action ではなく onSubmit で実行させる
  • useActionState のアクションは startTransition の中で実行する

これで煩雑なフォームの状態管理をuseActionStateに任せつつ、ReactAriaComponentの <Form> のエラーメッセージ表示など便利な機能を使うことができます。

リセットもしたい場合

上記の例だとステートが維持されるため入力内容はそのままです。ただ、時にはフォームを完全にリセットさせたいときもあります。その場合は requestFormReset() で手動リセットすることができます。

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    startTransition(() => {
      requestFormReset(event.currentTarget); // formオブジェクトを渡しリセット
      formAction(formData);
    });
  };

これでフォームリセットできます。ただこの実装だと常にリセットしてしまい、バリデーションエラー時に困った挙動になるため条件分岐などが必要になる点に注意してください。

ムーザルちゃんねる

Discussion