
【React19】サクッと理解するuseOptimistic
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、戸田です。
React19 からuseOptimisticという新しい hooks が登場しました。
今後使われることがある hooks だと思うのでまとめてみました。
以下が useOptimistic の公式リファレンスです。
useOptimistic とは
簡単な説明
useOptimisticは、ある非同期アクションが進行中の間だけ、楽観的更新をするための React フックです。具体的には、以下のようなシナリオで活用されます:
- フォームの送信:ユーザーがフォームを送信した際、サーバーのレスポンスを待たずに入力内容を即座に UI に反映させる。
- データの更新:ユーザーがデータを編集した際、変更を即座に反映させ、サーバーへの保存が完了した後に最終的なデータを確定する。
楽観的更新とは
useOptimistic を使う際における楽観的とは、API のデータ取得で想定する結果が返ってくるものとして考えることです。
白いボタンを押したら(本当はサーバーからレスポンスを待つ必要があるけど最終的に)赤色が変化するだろうから先に変更にしちゃえ!みたいな感じです
useOptimistic を使用して定義される state を楽観的 stateといいます
メリット
useOptimisticを使用すると実際の結果よりも先に楽観的な結果を即座に返却するため、ユーザー体験が非常に良くなります。
useOptimistic の 使い方
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
引数
state: 初期状態や、実行中のアクションが存在しない場合に返される値。updateFn(currentState, optimisticValue):currentState: 現在のステート値。optimisticValue:addOptimisticに渡された楽観的更新に使用する値。- この関数は、
currentStateとoptimisticValueを元に新しい楽観的ステートを返します。
返り値
optimisticState: 結果としての楽観的ステート。非同期アクションがない場合はstateと同一になり、アクションが実行中の場合はupdateFnが返す値となります。addOptimistic: 楽観的な更新を行うためのディスパッチ関数。任意の型の引数optimisticValueを 1 つ受け取り、updateFnを通じてステートが更新されます。
使用例: いいねボタンの実装
具体的な使用例を通じて、useOptimisticを説明します。
以下は X(旧 Twitter)でいいね ❤️ をする際の楽観的更新の例です。
import { startTransition, useOptimistic, useState } from "react";
const sendLikeAction = async (): Promise<string> => {
try {
// ダミー送信(3秒 delay)
await new Promise((resolve) => setTimeout(resolve, 3000));
return "red";
} catch (error) {
console.error(error);
return "white";
}
};
const optimisticAction = (_currentState: string, optimisticColor: string) => {
// ここで楽観的な処理をする
return optimisticColor;
};
export const LikeButton = () => {
const [color, setColor] = useState<string>("white");
const [displayLikeColor, addOptimistic] = useOptimistic<string, string>(color, optimisticAction);
const handleLikeClick = () => {
// 非同期アクションが進行中と示すためにstartTransitionを使用している(useActionStateやuseTransition内でも使用可能)
startTransition(async () => {
// 楽観的に表示する際の色を指定(yellowになる)
addOptimistic("yellow");
// データを取得してから元のstateを更新
const sendedIsLike = await sendLikeAction();
setColor(sendedIsLike);
});
// transitionが終わったので楽観的な表示を解除(redになる)
};
return (
<div>
<h3>誰かの投稿</h3>
<div>
<span onClick={handleLikeClick} style={{ color: displayLikeColor }}>
♡
</span>
</div>
</div>
);
};
コメントなし
import { startTransition, useOptimistic, useState } from "react";
const sendLikeAction = async (): Promise<string> => {
try {
await new Promise((resolve) => setTimeout(resolve, 3000));
return "red";
} catch (error) {
console.error(error);
return "white";
}
};
const optimisticAction = (_currentState: string, optimisticColor: string) => {
return optimisticColor;
};
export const LikeButton = () => {
const [color, setColor] = useState<string>("white");
const [displayLikeColor, addOptimistic] = useOptimistic<string, string>(color, optimisticAction);
const handleLikeClick = () => {
startTransition(async () => {
addOptimistic("yellow");
const sendedIsLike = await sendLikeAction();
setColor(sendedIsLike);
});
};
return (
<div>
<h3>誰かの投稿</h3>
<div>
<span onClick={handleLikeClick} style={{ color: displayLikeColor }}>
♡
</span>
</div>
</div>
);
};
コードの解説
今回の例では、useOptimistic フックを使って「いいね」ボタンの色を楽観的に更新し、ユーザーに迅速なフィードバックを提供しています。以下に、コードの各部分とその動作について簡単に説明します。
動作確認動画
この動画では分かりやすくするために表示を少し変えています。
流れ
useStateで初期のハートの色を"white"にするuseOptimisticに 初期値となる color と、楽観的更新を行うoptimisticActionを渡す- いいね(🤍)がクリックされる
addOptimisticの引数に"yellow"を渡して、楽観的更新をする(ハートが黄色になる 💛)- ダミー送信をする(3 秒 delay)
- 送信が終わった後、返却された値("red"❤️ or "white"🤍)の色に UI を更新する
startTransition を使用した理由
「useOptimistic とは」の中で以下のように説明しました。
useOptimistic はある非同期アクションが進行中の間だけ楽観的更新をするための React フックです。
startTransition の中で行われたステート更新はトランジションであると見なされます。
トランジションとしてマークされた state 更新は、他の state 更新によって中断されます。
なので3秒後に useState で更新した state(color)が displayLikeColor より優先度が高いため、color が更新された時点で displayLikeColor も更新されました。
このようにトランジション状態にするためにstartTransitionを使用しました。
また、startTransition を使わなくてもuseActionStateやuseTransitionを使うことでもトランジション状態を作れます。
// useActionState
const fn = () => {
// トランジション状態
};
const [state, formAction] = useActionState(fn, initialState, permalink);
// useTransition
const [isPending, startTransition] = useTransition();
startTransition(() => {
// トランジション状態
});
より詳しく知りたい方はこちらの本がおすすめです。
エラーハンドリングの重要性
楽観的な UI 更新では、サーバーからのレスポンスを待たずに UI を即座に更新するため、エラーが発生した場合の対処が重要です。今回の例では、エラーが発生した場合にボタンの色を白に戻し、コンソールにエラーメッセージを表示しています。実際のアプリケーションでは、ユーザーにエラーメッセージを表示するなど、さらに慎重なエラーハンドリングが求められます。
まとめ
useOptimistic フックを活用することで、非同期アクションが完了する前に UI を即座に更新し、ユーザーにスムーズな操作感を提供できます。この手法は、「いいね」ボタンだけでなく、フォームの送信やデータの編集など、さまざまなシナリオで有効に活用できます。ぜひ活用してみてください。
最後に、前回「サクッと理解する useActionState」という記事も書いたのでぜひ見てください。
参考リンク







