1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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 フックは様々なシナリオで活用できます:

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

結論

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?