前提
- Server Actionsが何かという話はしません。
- 本記事で扱うServer Actionsは、Custom Invocation (startTransitionで非同期関数を挟んで実行すること)ではなく、Progressive Enhancement (formのactionに紐づけて使用するServer Actionsのこと) を対象とします。
モチベーション
form
タグのaction
には一つのServer Actionsしか紐づけることができない。例えば以下のように、登録のほかにサーバ問い合わせが必要なボタンが存在する場合、少し工夫してあげる必要がある。
formActionsに紐づける方法
実は、formAction
> action
であると、みんな大好きMDNに書いてある。
action: フォーム経由で送信された情報を処理するプログラムの URL。この値は (中略) formaction 属性によって上書きすることが可能です。
ということは、2つのServer Actionsを用意して、それぞれ異なるActionを呼んでやればよいはず。ということでサンプルを以下に示す。
"use client";
import { checkName, register } from "./actions";
import { experimental_useFormState as useFormState } from "react-dom";
export function Form() {
const [stateCheckName, dispatchCheckName] = useFormState(checkName, "");
const [stateRegister, dispatchRegister] = useFormState(register, "");
return (
<form action={dispatchRegister}> // 登録用Server Actions
ユーザ名 <input type="text" name="name" />
<button type="submit" formAction={dispatchCheckName}> // 名前確認用 Serer Actions
使用可能か判定する
</button>
{stateCheckName}
<br />
パスワード <input type="password" name="password" />
<br />
<button type="submit">登録</button>
{stateRegister}
</form>
);
}
codesandbox版はこちら。
ただこれだと、名前の確認のためだけに不要なパスワードも送られてしまうので、改善の余地はありそう。
おまけ
おまけで、試してみたけどダメだったものを紹介する。2つフォームを作ってそれぞれのフォームにactionを紐づける方法だ。残念ながらフォーム in フォームはできないのでちょっと工夫する。
フォームの中にフォームを入れ子にすることは厳格に禁じられています。
しかし、buttonにはformがあり、これで任意のフォームを指定することができる。
form: この属性によって 要素が の中になくても、同一文書内にある任意の 要素に関連付けることが可能になりました。また、祖先の 要素を上書きすることができます。
これを試した結果が以下のサンプルコードである。しかし、残念ながらdispatchCheckName
のアクションで、name
を取得することはできない。inputは複数のformに紐づくことができないからである。
入力された値が必要のないケースであればこの方法は使えそうである。
"use client";
import { checkName, register } from "../actions";
import { experimental_useFormState as useFormState } from "react-dom";
export function Form() {
const [stateCheckName, dispatchCheckName] = useFormState(checkName, "");
const [stateRegister, dispatchRegister] = useFormState(register, "");
return (
<div>
<form action={dispatchRegister}>
ユーザ名 <input type="text" name="name" />
<button type="submit" form="2nd"> // formを指定することで、別のformのactionを発火
使用可能か判定する
</button>
{stateCheckName}
<br />
パスワード <input type="password" name="password" />
<br />
<button type="submit">登録</button>
{stateRegister}
</form>
<form id="2nd" action={dispatchCheckName}></form> // nameが送信されない!!!
</div>
);
}