今回もReact 19 Betaの新機能を解説です。3回目はActionsの useOptimistic Hookです。
ドキュメントには、
useOptimistic は、何らかの非同期アクションが進行中の間だけ、異なる state を表示するための React フックです。ある state を引数として受け取ってそのコピーを返しますが、ネットワークリクエストなどの非同期アクションが実行中の場合に異なる値を返すことができます。現在の state とアクションへの入力を受け取り、アクション実行中に使用される楽観的 state を返すような関数を渡します。
Bing Image Creatorが生成した画像を使っています
useOptimisticとは
useOptimisticのわかりやすい例は、カウンター付きのいいねボタンではないでしょうか。
いいねボタンの動作は、
- いいねボタンが押される
- サーバー(バックエンド)の
いいねボタン処理
が呼び出される - サーバー内に保存されている
いいね数
が+1される - サーバーからは最新の
いいね数
が戻る - 画面に最新の
いいね数
を表示する
ここで、2〜4の間にいいね数
をどうするかですが、
- 5まで変更されないのは、ボタンを押した反応が無いので良くないですね
- ローディング・アニメーション等を表示するのは大げさかもしれませんね
- 取りあえず現在ブラウザーに表示されている
いいね数
+1を表示するのが良さそうですね、5で表示されるいいね数
が+2だとしても問題ないですよね
このような機能を実装するのに使えるのが useOptimistic Hookです。
今回のサンプルコード
今回もジャンケンです😃、以前のブログ React19のActions, useActionState Hookを使ってみたで作ったジャンケンアプリの改良版です。
下の画面画像のように、ポンボタンを押した時点で、コンピューターの手、勝敗に⌛ ...
が表示されまれ。そしてアクションが完了した時点で正式な値が表示されます。
コード解説
React19のActions, useActionState Hookを使ってみたのコードをチョコチョコ変更しています。
準備
- JyankenType.ts
ジャンケンの手に未定義、勝敗に判定中の状態を追加しました。
export const Te = {
Guu: 0,
Choki: 1,
Paa: 2,
Undefine: 3 // ← 手が判らない状態の追加
} as const;
・・・
export const Judgment = {
Draw: 0,
Win: 1,
Lose: 2,
Judging: 3 // ← 判定中の状態の追加
} as const;
- ScoreBox.tsx
⌛を表示するためにデータを追加しました。
export default function ScoreBox ({scores}: ScoreBoxProps ) {
const teString = ["グー", "チョキ", "パー", "⌛ ..."]; // ← 追加
const judgmentString = ["引き分け", "勝ち", "負け", "⌛ 判定中 ..."]; // ← 追加
Jyanken.tsx
メインのコードです。ここでuseOptimistic
が使われます。
- ① ponAction関数に
useOptimistic
を加えたoptimisticPonAction関数の定義 - ② 楽観的な更新を行うaddOptimisticScores関数を呼出します、引数にはフォームデータ・オブジェクトから取得したジャンケンの手を渡しています
- ③ ここで、本来の処理ponAction関数を呼出します
- ④ useActionStateの呼出し、アクションがoptimisticPonActionに変更されています
- ⑤ 楽観的な更新を行う関数定義
- 引数は、
- state: 現在のステート
- action: ②で渡された引数、ここではジャンケンの手
- Stateの先頭に楽観的な更新値を連結
- human: 人間の手はactionで渡された値
- computer: コンピューターの手は判らないので
Te.Undefine
- judgment: 勝敗も判らないので
Judgment.Judging
- 引数は、
- ⑥ useOptimisticの呼出し、
- テンプレートにはStateの型、アクションの引数の型
- 引数は、現在のStateと楽観的な更新を行う関数
- ⑦ ScoreBoxコンポーネントには楽観的なState、optimisticScoresを渡します
"use client";
import ScoreBox from '../_components/ScoreBox';
import JyankenBox from '../_components/JyankenBox';
import { useActionState, useOptimistic } from 'react';
import { ScoreType, Te, Judgment } from '../_libs/JyankenType';
import { ponAction } from '../_libs/JyankenAction';
type JyankenProps = {
defaultScores: ScoreType[]
}
export default function Jyanken ({defaultScores}: JyankenProps) {
// ↓ ①
const optimisticPonAction = async (prevState: ScoreType[], formData: FormData):
Promise<ScoreType[]> => {
addOptimisticScores(Number(formData.get("te")) as Te); // ← ②
return await ponAction(prevState, formData); // ← ③
}
const [scores, formAction] = // ↓ ④
useActionState<ScoreType[], FormData>(optimisticPonAction, defaultScores);
const updateOptimistic = (state: ScoreType[], action: Te) => // ← ⑤
[{human: action, computer: Te.Undefine, judgment: Judgment.Judging,
comment: null}, ...state];
const [optimisticScores, addOptimisticScores] = // ← ⑥
useOptimistic<ScoreType[], Te>(scores, updateOptimistic);
return (
<>
<h1>じゃんけん ポン!</h1>
<JyankenBox formAction={formAction} />
<ScoreBox scores={optimisticScores} /> {/* ← ⑦ */}
</>
);
}
まとめ
通常のアクション処理は useActionState を使い、ローディング・アニメーション等を表示すれば良いと思われます。
しかし、ゲームやカウンター付きのいいねボタンなどのように直ぐにフィードバックを表示したほうが良い場合は、useOptimistic Hookを使うと良いと思います。
useActionState
を使ったコードなら
- アクションに楽観的更新の呼出しを追加したアクションの作成
- 楽観的更新のStateを戻す関数の作成
useOptimistic
呼出し- 楽観的Stateへの切り替え
くらいの僅かな変更で対応できます。useActionState
、useOptimistic
の関係が少し判りにくいところもありますが定型パターンなので一度作れば理解できると思います。