Open18

Server ActionsやuseFormStateやuseOptimisticなどを使ってみる雑記

かがんかがん

App Routerの環境を触っていながら全然使ってこなかったので、個人開発で使ってみてる。

ムーザルチャンネル見てよさを理解できたから。

https://youtu.be/qUt-YP4nlNA?si=Em1N-Ga7HjnplaSk
https://zenn.dev/moozaru/articles/e101286259a878

使ってみたらめちゃ便利な感じでワクワクする。
まず型が自然に推論されるのが嬉しい。しかもimportしてるから、参照から使ってるかどうか・どこで使ってるか調べられるの嬉しい。(API Routesはfetchだから直接参照(import)しないから、どこで使ってるかわからなくなる)

Server Componentなど含めての流れとしてだけど、これまでなにもかもクライアントでやっていたのを徐々にサーバーでやろうという流れいいなあ
=端末のスペック足りなくてもサーバーで処理できるよ、JSがなくても動くよという感じで、可能性を感じる

かがんかがん

Server Actions

  • formのためだけのものだと思っていたが、どうやらそうではない
  • クライアントから呼び出しつつ、サーバーで実行することができる処理
    • 実態は fetch の糖衣構文みたいなイメージ?
  • formに使うと、JSが無効な環境でも使えるようにできる
    • 正直JSが無効な環境をサポートするのって現実的なの…?みたいなこと思ったりするけど、JSがなくても動くってことだけでワクワクする
  • form以外でも、普通にイベントハンドラやuseEffectの中でもつかえる
    • ただしこの場合はJSが無効な環境で動かすのは無理

https://zenn.dev/rio_dev/articles/eb69fae0557f20
https://azukiazusa.dev/blog/why-use-server-actions

かがんかがん

useOptimistic

  • optimistic = 楽観的
  • 非同期処理を待たずに更新したいとき使う
  • いいね状態の反映みたいな「エラー起きる可能性はあるけど多分成功するよね」な処理とか、「読み込み中の状態で入れておきたい」など

react.devのサンプルがわかりやすい
https://ja.react.dev/reference/react/useOptimistic#usage

かがんかがん

useFormStateuseOptimistic をあわせて使う

うまいことやらないと、JS無効環境でもつかえるよさがなくなってしまうので注意。

こちらを参考にした。

https://github.com/unstubbable/mfng/pull/38/files

JS有効の場合だけ処理する楽観的更新は、onSubmitでイベントリスナーとして設定する(useFormStateaction 属性には入れない)

かがんかがん

useFormState の stateを複数持つことはできる?

まず問題番号を選択、その下で解答を受け取る、のように、「問題番号」と「解答」という2つのstateを持ちたかった
が、これをJS無効環境で動かすのはさすがに無理だった(結局内部はformの送信なので当たり前)
問題番号の選択はできても、解答を送ったタイミングで問題番号の選択がリセットされる。

1つのstateにまとめる必要がある。

もしくは、別ページにできるような仕様の場合、 /quiz/[quizId] 的なページにしてしまうのもあり

かがんかがん

送信時に入力をクリアしたい

ChatGPTのように、メッセージを送ったらその場でテキストボックスが空になってほしい。
しかもuseFormStateでJS無効でも動くと嬉しい。

なにも考えずに送信時にクリアしてしまうと、Server Actionsで処理される値もクリアされてしまうため、処理が失敗してしまう。

それっぽいDiscussion:
https://github.com/vercel/next.js/discussions/58448

これでうまくいった、のか…?
仕組みを何も理解してないからだいぶあやしい

  // 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 が変わったときにフォームをリセットする、というのがあったので、これを使って解決できそう
https://ja.conform.guide/api/react/useForm#tips

  const [clearFormKey, setClearFormKey] = useState(0);

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    setClearFormKey((prev) => prev + 1);
    // ...
  };

  const [form, fields] = useForm({
    id: `${clearFormKey}`,
    // ...
  });
かがんかがん

バリデーション

JS無効環境考慮しつつ、クライアントサイドのバリデーションもしたい

サーバーからエラーを返す手法がいいらしい?
https://azukiazusa.dev/blog/use-form-state-to-display-error-messages-in-server-actions-forms/

うーん、送信する前にエラー確認したいけどなー

送信前にServer Action実行する例もあったけど、リクエスト走るのもどうなんだろう
https://cam-inc.co.jp/p/techblog/859745503506595841

かがんかがん

必須メッセージが上書きできない…?

 message: z.string().min(1, { message: "回答を入力してください。" }),

としても、 Required が表示されてしまう

 message: z.string({ required_error: "回答を入力してください。" }).min(1, { message: "回答を入力してください。" }),

だった。minの方のmessageはいらないかもだけど一応入れとく

かがんかがん

defaultNoValidate オプションいいやん…!

これを false にしてあげることで、「JS有効ならnovalidateをつけてJSだけで検証する」「JS無効ならHTMLのデフォルト機能で検証する」ができる!
自前で切り替えようとしてたけどこのオプションだけでよかった

https://ja.conform.guide/api/react/useForm#オプション

でもなんでデフォルト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でもこれに関しての言及をいくつか見た。
https://x.com/mizdra/status/1791297585438969951
https://x.com/uhyo_/status/1768071895860285743

たとえば、View Transitions API みたいなものを活用するときに、未対応のブラウザではアニメーションなしではあるが進行はできるようにしつつ、実装済みのブラウザではアニメーションが効くようにする、みたいなものがプログレッシブエンハンスメントだと思う。
一方で、JS無効の環境で動かすことはWebアプリケーションにおいてそもそも想定していない状況なはずなので、このケースで使うのは違和感がある。

JS無効でも使えるよ~ってことを言いたいのであれば、 Works without JavaScript とか、違うワードを用いたほうが自然な気がする。