React useOptimisticで即座にUIを更新する方法
問題
「いいね」ボタンを押した時、サーバーのレスポンスが返ってくるまでUIが止まってしまう問題があります。ユーザーにとって、ボタンを押しても0.5秒間反応がないと「押せたのかな?」と不安になります。ローディングスピナーを表示するのは大げさですし、即座に反映されてほしいところです。
解決方法
React 19で追加されたuseOptimisticフックを使えば、サーバーレスポンスの前にUIを先に更新し、失敗した場合は自動的にロールバックできます。
import { useOptimistic } from 'react';
function LikeButton({ postId, initialCount }: {
postId: string;
initialCount: number;
}) {
const [optimisticCount, setOptimisticCount] = useOptimistic(
initialCount,
(current, increment: number) => current + increment
);
async function handleLike() {
setOptimisticCount(1); // UIを即座に更新
await likePost(postId); // サーバーリクエストはバックグラウンドで
}
return (
<button onClick={handleLike}>
{optimisticCount}
</button>
);
}
より実践的な例として、コメントリストに適用するとこのようになります:
function CommentList({ comments }: { comments: Comment[] }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(state, newComment: Comment) => [...state, newComment]
);
async function handleSubmit(text: string) {
const tempComment = {
id: crypto.randomUUID(),
text,
pending: true,
};
addOptimisticComment(tempComment); // リストに即座に追加
await postComment(text); // サーバーに保存
}
return (
<ul>
{optimisticComments.map((c) => (
<li key={c.id} style={{ opacity: c.pending ? 0.6 : 1 }}>
{c.text}
</li>
))}
</ul>
);
}
ポイント
useOptimisticは非同期処理が進行中の間だけ楽観的な状態を表示し、完了すると実際の状態に戻ります- サーバーリクエストが失敗した場合は、元の状態に自動的にロールバックされるのでエラー処理が簡単です
pendingフラグを使って「保存中」の状態を視覚的に表現すると、UXがさらに向上します