React 19 では、非同期処理中に UI をすぐに更新するための新しいフック useOptimistic が登場しました。このフックを使うことで、ユーザーの操作に対してすぐに反映を行い、ネットワーク応答を待たずにスムーズな体験を実現できます。
useOptimistic とは?
useOptimistic フックは、非同期処理が進行中の間、一時的に実際の状態とは異なる「楽観的」な状態を管理することを可能にします。例えば、ユーザーが新しいタスクを追加する際、API のレスポンスを待たずに期待される結果を即座に UI に反映します。バックグラウンドで API 呼び出しが行われ、処理が完了したら UI を実際のデータに合わせて更新することができます。
このフックの使用方法は次の通りです:
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
- state: 現在の状態です。
- updateFn: 現在の状態と楽観的な値を受け取り、期待される新しい状態を返す純粋な関数です。
この手法は、フォーム送信、メッセージング、その他ユーザーの反応を即時に示す必要があるシーンで非常に役立ちます。
コード比較: useOptimistic 使用前と使用後
使用前
従来の方法では、ユーザーが新しいタスクを追加すると、API のレスポンスを待ってから状態を更新します:
'use client'
import { useRef, useState } from 'react'
interface Todo {
id: string
text: string
}
async function apiUploadTodo(text: string): Promise<Todo> {
// 1秒の遅延で API レイテンシーをシミュレーション
await new Promise((resolve) => setTimeout(resolve, 1000))
return {
id: Math.random().toString(36).slice(2, 9),
text
}
}
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([])
const textFieldRef = useRef<HTMLInputElement>(null)
async function onClick() {
const addedTodo = await apiUploadTodo(textFieldRef.current?.value || '')
setTodos((todos) => [...todos, addedTodo])
}
return (
<>
<input type='text' name='todo' placeholder='New Todo' ref={textFieldRef} />
<button onClick={onClick}>追加</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
)
}
export default TodoList


この方法では、API 呼び出しが完了するまで UI に変更が反映されないため、ネットワーク遅延がユーザー体験に影響を与える可能性があります。
使用後
useOptimistic を使用することで、非同期処理中にも即座に楽観的な状態で UI を更新し、ローディング状態などの視覚的なフィードバックを提供できます:
'use client'
import { startTransition, useOptimistic, useRef, useState } from 'react'
interface Todo {
id: string
text: string
loading?: boolean
}
async function apiUploadTodo(text: string): Promise<Todo> {
// 1秒の遅延で API レイテンシーをシミュレーション
await new Promise((resolve) => setTimeout(resolve, 1000))
return {
id: Math.random().toString(36).slice(2, 9),
text
}
}
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([])
const [optimisticTodos, addOptimisticTodo] = useOptimistic<Todo[], string>(todos, (state, text) => [
...state,
{
id: Math.random().toString(36).slice(2, 9),
text,
loading: true
}
])
const textFieldRef = useRef<HTMLInputElement>(null)
async function onClick() {
startTransition(async () => {
if (textFieldRef.current === null) {
return
}
// 楽観的に UI を更新する
addOptimisticTodo(textFieldRef.current?.value || '')
// 非同期処理を実行する
const resultItem = await apiUploadTodo(textFieldRef.current?.value || '')
// 楽観的なエントリーを実際のデータに置き換える
setTodos((todos) => [...todos, resultItem])
})
}
return (
<>
<input type='text' name='todo' placeholder='New Todo' ref={textFieldRef} />
<button onClick={onClick}>追加</button>
<ul>
{optimisticTodos.map((todo) => (
<li key={todo.id}>
{todo.text}
{todo.loading && <small> (追加中...)</small>}
</li>
))}
</ul>
</>
)
}
export default TodoList


この改善されたバージョンでは、ユーザーがタスクを追加するとすぐにリストに項目が表示され、「(追加中...)」のラベルが付くことで処理中であることが分かります。これにより、ユーザーは即時のフィードバックを得ることができ、全体の体験が向上します。
useOptimistic のメリット
即時フィードバックとスムーズな UX
- 応答性の向上: ユーザーの操作に対して即座に変更を反映することで、ネットワーク遅延が感じられなくなります
- 視覚的な手がかり: ローディングインジケーターなどの視覚的な要素があることで、ユーザーは自分の操作が処理中であると認識できます
- エンゲージメントの向上: UI の即時反映により、ユーザーは操作が確実に反映されていると感じ、アプリケーションとのインタラクションが促進されます
useOptimistic を使わない場合のフォールバック
useOptimistic を使用しない場合、非同期操作が完了するまで UI に変更が反映されず、以下の問題が発生する可能性があります:
- フィードバックの遅延: ユーザーは変更が反映されるまで待たなければならず、複数回クリックしてしまう可能性があります
- 混乱: 操作が登録されたかどうかのフィードバックがないため、ユーザーは操作が失敗したのかどうか疑問に思うかもしれません
- パフォーマンスの印象低下: 実際の処理速度が速くても、ユーザー体験としては遅く感じられる可能性があります
useOptimistic のユースケース
useOptimistic フックは様々なシナリオで活用できます:
-
フォームと入力の送信:
- コメント、メッセージ、投稿など、フォーム送信時に UI を即時更新してユーザーに反映させる
-
リアルタイムデータの更新:
- チャットや通知など、即時更新が求められる機能に最適
-
インタラクティブなリスト:
- Todo リストやショッピングカートなど、項目の追加・削除・更新で即時のフィードバックを提供
-
投票や「いいね」機能:
- ユーザーが投票や「いいね」を押した際、即座に結果を反映することでインタラクションを強化
-
共同作業アプリ:
- 共同編集ツールや共有タスク管理アプリでは、楽観的更新により応答性が向上し、複数ユーザー間の同期がスムーズに行えます
結論
React 19 の useOptimistic フックは、非同期処理から UI 更新を切り離すことで、より動的で応答性の高いユーザー体験を実現するための強力なツールです。フォーム送信、チャットアプリ、インタラクティブなリストなど、どのようなシーンでも即時フィードバックを提供し、ユーザーの操作を確実に反映します。
useOptimistic を活用することで、ネットワーク遅延の影響を最小限に抑え、より直感的で快適なインタラクションが可能となります。ぜひこの新機能を取り入れて、React アプリケーションのユーザー体験を向上させましょう!