Server ActionsやuseFormStateやuseOptimisticなどを使ってみる雑記
App Routerの環境を触っていながら全然使ってこなかったので、個人開発で使ってみてる。
ムーザルチャンネル見てよさを理解できたから。
使ってみたらめちゃ便利な感じでワクワクする。
まず型が自然に推論されるのが嬉しい。しかもimportしてるから、参照から使ってるかどうか・どこで使ってるか調べられるの嬉しい。(API Routesはfetchだから直接参照(import)しないから、どこで使ってるかわからなくなる)
Server Componentなど含めての流れとしてだけど、これまでなにもかもクライアントでやっていたのを徐々にサーバーでやろうという流れいいなあ
=端末のスペック足りなくてもサーバーで処理できるよ、JSがなくても動くよという感じで、可能性を感じる
Server Actions
- formのためだけのものだと思っていたが、どうやらそうではない
- クライアントから呼び出しつつ、サーバーで実行することができる処理
- 実態は
fetch
の糖衣構文みたいなイメージ?
- 実態は
- formに使うと、JSが無効な環境でも使えるようにできる
- 正直JSが無効な環境をサポートするのって現実的なの…?みたいなこと思ったりするけど、JSがなくても動くってことだけでワクワクする
- form以外でも、普通にイベントハンドラやuseEffectの中でもつかえる
- ただしこの場合はJSが無効な環境で動かすのは無理
useFormState
- 最新では
useActionState
になっている
useOptimistic
- optimistic = 楽観的
- 非同期処理を待たずに更新したいとき使う
- いいね状態の反映みたいな「エラー起きる可能性はあるけど多分成功するよね」な処理とか、「読み込み中の状態で入れておきたい」など
react.devのサンプルがわかりやすい
useFormState
と useOptimistic
をあわせて使う
うまいことやらないと、JS無効環境でもつかえるよさがなくなってしまうので注意。
こちらを参考にした。
JS有効の場合だけ処理する楽観的更新は、onSubmitでイベントリスナーとして設定する(useFormState
や action
属性には入れない)
useFormState の stateを複数持つことはできる?
まず問題番号を選択、その下で解答を受け取る、のように、「問題番号」と「解答」という2つのstateを持ちたかった
が、これをJS無効環境で動かすのはさすがに無理だった(結局内部はformの送信なので当たり前)
問題番号の選択はできても、解答を送ったタイミングで問題番号の選択がリセットされる。
1つのstateにまとめる必要がある。
もしくは、別ページにできるような仕様の場合、 /quiz/[quizId]
的なページにしてしまうのもあり
送信時に入力をクリアしたい
ChatGPTのように、メッセージを送ったらその場でテキストボックスが空になってほしい。
しかもuseFormStateでJS無効でも動くと嬉しい。
なにも考えずに送信時にクリアしてしまうと、Server Actionsで処理される値もクリアされてしまうため、処理が失敗してしまう。
それっぽいDiscussion:
これでうまくいった、のか…?
仕組みを何も理解してないからだいぶあやしい
// createRefだと送信時にクリア、useRefだと結果が返ってきたときにクリアされる
const formRef = createRef<HTMLFormElement>();
// const formRef = useRef<HTMLFormElement>(null);
useEffect(() => {
formRef.current?.reset();
}, [formRef, state]);
あ、これって結局こうしてるだけじゃん
const formRef = useRef<HTMLFormElement>(null);
useEffect(() => {
formRef.current?.reset();
});
conformの機能で、id
が変わったときにフォームをリセットする、というのがあったので、これを使って解決できそう
const [clearFormKey, setClearFormKey] = useState(0);
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
setClearFormKey((prev) => prev + 1);
// ...
};
const [form, fields] = useForm({
id: `${clearFormKey}`,
// ...
});
あー、でもこれフォーカスも消えちゃうな
バリデーション
JS無効環境考慮しつつ、クライアントサイドのバリデーションもしたい
サーバーからエラーを返す手法がいいらしい?
うーん、送信する前にエラー確認したいけどなー
送信前にServer Action実行する例もあったけど、リクエスト走るのもどうなんだろう
Server Actionsと合わせるならconformがいいらしいという話を見た
うーん、うまくいかない、これと同じエラーが出る
→上記のDisucussionとは関係なくって、 id={form.id}
をつけ忘れてるだけだった
conformのドキュメント、日本語あるじゃん! 嬉しい
条件分岐のフォームをバリデーションしたいとき、 .discriminatedUnion
を使わないとダメだった
必須メッセージが上書きできない…?
message: z.string().min(1, { message: "回答を入力してください。" }),
としても、 Required
が表示されてしまう
↓
message: z.string({ required_error: "回答を入力してください。" }).min(1, { message: "回答を入力してください。" }),
だった。minの方のmessageはいらないかもだけど一応入れとく
defaultNoValidate
オプションいいやん…!
これを false
にしてあげることで、「JS有効ならnovalidateをつけてJSだけで検証する」「JS無効ならHTMLのデフォルト機能で検証する」ができる!
自前で切り替えようとしてたけどこのオプションだけでよかった
でもなんでデフォルトtrueなんだろう…
(余談)JS無効でも動く、ということを Progressive Enhancement と呼ぶことについて
Next.jsのドキュメントでは、「Server ActionsはProgressive Enhancement をサポートしてます、すなわちJS無効環境でもフォームが動きます」という説明をしている様子。
Server Components support progressive enhancement by default, meaning the form will be submitted even if JavaScript hasn't loaded yet or is disabled.
https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#behavior
この表現には違和感があって、このスクラップでも、プログレッシブエンハンスメントという表現は避けて「JS無効環境でも動く」という表現を使った。
Twitterでもこれに関しての言及をいくつか見た。
たとえば、View Transitions API みたいなものを活用するときに、未対応のブラウザではアニメーションなしではあるが進行はできるようにしつつ、実装済みのブラウザではアニメーションが効くようにする、みたいなものがプログレッシブエンハンスメントだと思う。
一方で、JS無効の環境で動かすことはWebアプリケーションにおいてそもそも想定していない状況なはずなので、このケースで使うのは違和感がある。
JS無効でも使えるよ~ってことを言いたいのであれば、 Works without JavaScript とか、違うワードを用いたほうが自然な気がする。