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

Undo(取り消し)パターン

CATEGORY開発パターン TYPEReact Pattern EFFORT60〜180分 DIFFICULTY
PRIMARY CODE
tsx
import { useRef } from 'react';

// Undo フック(5秒以内なら取り消し可能・期限後にcommit)
export function useUndoableAction<T>(commitMs = 5000) {
  const pending = useRef<{ undo: () => void; commit: () => Promise<void>; timer: ReturnType<typeof setTimeout> } | null>(null);

  const start = (
    optimistic: () => void,
    commit: () => Promise<void>,
    undo: () => void,
  ): { cancel: () => void } => {
    optimistic();
    if (pending.current) { clearTimeout(pending.current.timer); pending.current.commit(); }
    const timer = setTimeout(async () => { await commit(); pending.current = null; }, commitMs);
    pending.current = { undo, commit, timer };
    return {
      cancel: () => {
        if (!pending.current) return;
        clearTimeout(pending.current.timer);
        pending.current.undo();
        pending.current = null;
      },
    };
  };

  return { start };
}

// 使い方:
// const { start } = useUndoableAction();
// const handleDelete = (item) => {
//   const action = start(
//     () => removeFromUI(item),
//     () => api.delete(item.id),
//     () => addBackToUI(item),
//   );
//   toast({ msg: '削除しました', action: { label: '取消', onClick: action.cancel } });
// };
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • 削除取り消し
  • バルク操作取り消し

Undo(取り消し)パターン

:LiTarget: 用途

削除・編集を取り消せるUndoパターン。トースト+タイマー方式。

:LiSparkle: 特徴

  • トースト連携
  • タイマー
  • バックエンド連携

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

import { useRef } from 'react';

// Undo フック(5秒以内なら取り消し可能・期限後にcommit)
export function useUndoableAction<T>(commitMs = 5000) {
  const pending = useRef<{ undo: () => void; commit: () => Promise<void>; timer: ReturnType<typeof setTimeout> } | null>(null);

  const start = (
    optimistic: () => void,
    commit: () => Promise<void>,
    undo: () => void,
  ): { cancel: () => void } => {
    optimistic();
    if (pending.current) { clearTimeout(pending.current.timer); pending.current.commit(); }
    const timer = setTimeout(async () => { await commit(); pending.current = null; }, commitMs);
    pending.current = { undo, commit, timer };
    return {
      cancel: () => {
        if (!pending.current) return;
        clearTimeout(pending.current.timer);
        pending.current.undo();
        pending.current = null;
      },
    };
  };

  return { start };
}

// 使い方:
// const { start } = useUndoableAction();
// const handleDelete = (item) => {
//   const action = start(
//     () => removeFromUI(item),
//     () => api.delete(item.id),
//     () => addBackToUI(item),
//   );
//   toast({ msg: '削除しました', action: { label: '取消', onClick: action.cancel } });
// };

:LiHandPointer: 使い方

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

:LiAlertCircle: 注意事項

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