React 19のuseOptimisticで楽々楽観的更新


React 19 では、非同期処理中に UI をすぐに更新するための新しいフック **useOptimistic** が登場しました。このフックを使うことで、ユーザーの操作に対してすぐに反映を行い、ネットワーク応答を待たずにスムーズな体験を実現できます。

---

## useOptimistic とは？

**useOptimistic** フックは、非同期処理が進行中の間、一時的に実際の状態とは異なる「楽観的」な状態を管理することを可能にします。例えば、ユーザーが新しいタスクを追加する際、API のレスポンスを待たずに期待される結果を即座に UI に反映します。バックグラウンドで API 呼び出しが行われ、処理が完了したら UI を実際のデータに合わせて更新することができます。

このフックの使用方法は次の通りです:
```jsx
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
```
- **state**: 現在の状態です。
- **updateFn**: 現在の状態と楽観的な値を受け取り、期待される新しい状態を返す純粋な関数です。

この手法は、フォーム送信、メッセージング、その他ユーザーの反応を即時に示す必要があるシーンで非常に役立ちます。

---

## コード比較: useOptimistic 使用前と使用後

### 使用前

従来の方法では、ユーザーが新しいタスクを追加すると、API のレスポンスを待ってから状態を更新します:
```jsx
'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
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981473/95988979-8d0e-4a5a-acc2-3d04d79c9213.png" width=50%>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981473/e39fa036-7e73-4ea1-9368-879a76c9cb28.png" width=50%>

この方法では、API 呼び出しが完了するまで UI に変更が反映されないため、ネットワーク遅延がユーザー体験に影響を与える可能性があります。

---

### 使用後

**useOptimistic** を使用することで、非同期処理中にも即座に楽観的な状態で UI を更新し、ローディング状態などの視覚的なフィードバックを提供できます:
```tsx
'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
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981473/92e29b8c-f701-4739-90ec-245bb85e5875.png" width=50%>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3981473/e4a981f5-e4d5-4531-b638-c5fde6928553.png" width=50%>

この改善されたバージョンでは、ユーザーがタスクを追加するとすぐにリストに項目が表示され、「(追加中...)」のラベルが付くことで処理中であることが分かります。これにより、ユーザーは即時のフィードバックを得ることができ、全体の体験が向上します。

## useOptimistic のメリット

### 即時フィードバックとスムーズな UX

- **応答性の向上:** ユーザーの操作に対して即座に変更を反映することで、ネットワーク遅延が感じられなくなります
- **視覚的な手がかり:** ローディングインジケーターなどの視覚的な要素があることで、ユーザーは自分の操作が処理中であると認識できます
- **エンゲージメントの向上:** UI の即時反映により、ユーザーは操作が確実に反映されていると感じ、アプリケーションとのインタラクションが促進されます

### useOptimistic を使わない場合のフォールバック

**useOptimistic** を使用しない場合、非同期操作が完了するまで UI に変更が反映されず、以下の問題が発生する可能性があります:
- **フィードバックの遅延:** ユーザーは変更が反映されるまで待たなければならず、複数回クリックしてしまう可能性があります
- **混乱:** 操作が登録されたかどうかのフィードバックがないため、ユーザーは操作が失敗したのかどうか疑問に思うかもしれません
- **パフォーマンスの印象低下:** 実際の処理速度が速くても、ユーザー体験としては遅く感じられる可能性があります

## useOptimistic のユースケース

**useOptimistic** フックは様々なシナリオで活用できます:
1. **フォームと入力の送信:**
   - コメント、メッセージ、投稿など、フォーム送信時に UI を即時更新してユーザーに反映させる
2. **リアルタイムデータの更新:**
   - チャットや通知など、即時更新が求められる機能に最適
3. **インタラクティブなリスト:**
   - Todo リストやショッピングカートなど、項目の追加・削除・更新で即時のフィードバックを提供
4. **投票や「いいね」機能:**
   - ユーザーが投票や「いいね」を押した際、即座に結果を反映することでインタラクションを強化
5. **共同作業アプリ:**
   - 共同編集ツールや共有タスク管理アプリでは、楽観的更新により応答性が向上し、複数ユーザー間の同期がスムーズに行えます

## 結論

React 19 の **useOptimistic** フックは、非同期処理から UI 更新を切り離すことで、より動的で応答性の高いユーザー体験を実現するための強力なツールです。フォーム送信、チャットアプリ、インタラクティブなリストなど、どのようなシーンでも即時フィードバックを提供し、ユーザーの操作を確実に反映します。  
**useOptimistic** を活用することで、ネットワーク遅延の影響を最小限に抑え、より直感的で快適なインタラクションが可能となります。ぜひこの新機能を取り入れて、React アプリケーションのユーザー体験を向上させましょう！