SCALE — Build Lab
開発パターン · REACT PATTERN

楽観的更新パターン

CATEGORY開発パターン TYPEReact Pattern EFFORT90〜240分 DIFFICULTY
PRIMARY CODE
tsx
import { useState, useCallback } from 'react';

// 楽観的更新フック(失敗時自動ロールバック)
export function useOptimistic<T>(initial: T) {
  const [value, setValue] = useState(initial);
  const [pending, setPending] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const update = useCallback(async (
    next: T,
    persist: () => Promise<T>,
  ) => {
    const prev = value;
    setValue(next);          // 楽観: 即UI反映
    setPending(true);
    setError(null);
    try {
      const result = await persist();
      setValue(result);      // 確定値で上書き
    } catch (e) {
      setValue(prev);        // ロールバック
      setError(e as Error);
    } finally {
      setPending(false);
    }
  }, [value]);

  return { value, update, pending, error };
}

// 使い方:
// const { value, update, error } = useOptimistic(task.done);
// <Checkbox checked={value} onChange={(v) =>
//   update(v, () => api.updateTask(task.id, { done: v }))
// } />
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • いいね機能
  • チェックボックス
  • インライン編集

楽観的更新パターン

:LiTarget: 用途

API呼出し前にUIを先に更新して、失敗時にロールバックするパターン。

:LiSparkle: 特徴

  • 即時UI反映
  • ロールバック
  • ローディング不要

:LiCode: コード(コピペ用)

import { useState, useCallback } from 'react';

// 楽観的更新フック(失敗時自動ロールバック)
export function useOptimistic<T>(initial: T) {
  const [value, setValue] = useState(initial);
  const [pending, setPending] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const update = useCallback(async (
    next: T,
    persist: () => Promise<T>,
  ) => {
    const prev = value;
    setValue(next);          // 楽観: 即UI反映
    setPending(true);
    setError(null);
    try {
      const result = await persist();
      setValue(result);      // 確定値で上書き
    } catch (e) {
      setValue(prev);        // ロールバック
      setError(e as Error);
    } finally {
      setPending(false);
    }
  }, [value]);

  return { value, update, pending, error };
}

// 使い方:
// const { value, update, error } = useOptimistic(task.done);
// <Checkbox checked={value} onChange={(v) =>
//   update(v, () => api.updateTask(task.id, { done: v }))
// } />

:LiHandPointer: 使い方

対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。

:LiAlertCircle: 注意事項

  • 依存パッケージを忘れず追加