Use Data Grid Undo Redo
tablecn/use-data-grid-undo-redoFreeHook
An undo/redo hook for the data grid with keyboard shortcuts and history management
Install it
npx shadcn@latest add https://tablecn.com/r/use-data-grid-undo-redo.jsonSource
1import * as React from "react";2import { toast } from "sonner";34import { useAsRef } from "@/hooks/use-as-ref";5import { useLazyRef } from "@/hooks/use-lazy-ref";6import { getIsInPopover } from "@/lib/data-grid";78const DEFAULT_MAX_HISTORY = 100;9const BATCH_TIMEOUT = 300;1011interface HistoryEntry<TData> {12 variant: "cells_update" | "rows_add" | "rows_delete";13 count: number;14 timestamp: number;15 undo: (currentData: TData[]) => TData[];16 redo: (currentData: TData[]) => TData[];17}1819interface UndoRedoCellUpdate {20 rowId: string;21 columnId: string;22 previousValue: unknown;23 newValue: unknown;24}2526interface StoreState<TData> {27 undoStack: HistoryEntry<TData>[];28 redoStack: HistoryEntry<TData>[];29 hasPendingChanges: boolean;30}3132interface Store<TData> {33 subscribe: (callback: () => void) => () => void;34 getState: () => StoreState<TData>;35 push: (entry: HistoryEntry<TData>) => void;36 undo: () => HistoryEntry<TData> | null;37 redo: () => HistoryEntry<TData> | null;38 clear: () => void;39 setPendingChanges: (value: boolean) => void;40 notify: () => void;41}4243function useStore<T>(44 store: Store<T>,45 selector: (state: StoreState<T>) => boolean,46): boolean {47 const getSnapshot = React.useCallback(48 () => selector(store.getState()),49 [store, selector],50 );5152 return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);53}5455function buildIndexById<TData>(56 data: TData[],57 getRowId: (row: TData) => string,58): Map<string, number> {59 const map = new Map<string, number>();60 for (let i = 0; i < data.length; i++) {61 const row = data[i];62 if (row) {63 map.set(getRowId(row), i);64 }65 }66 return map;67}6869function getPendingKey(rowId: string, columnId: string): string {70 return `${rowId}\0${columnId}`;71}7273interface UseDataGridUndoRedoProps<TData> {74 data: TData[];75 onDataChange: (data: TData[]) => void;76 getRowId: (row: TData) => string;77 maxHistory?: number;78 enabled?: boolean;79}8081interface UseDataGridUndoRedoReturn<TData> {82 canUndo: boolean;83 canRedo: boolean;84 onUndo: () => void;85 onRedo: () => void;86 onClear: () => void;87 trackCellsUpdate: (updates: UndoRedoCellUpdate[]) => void;88 trackRowsAdd: (rows: TData[]) => void;89 trackRowsDelete: (rows: TData[]) => void;90}9192function useDataGridUndoRedo<TData>({93 data,94 onDataChange,95 getRowId,96 maxHistory = DEFAULT_MAX_HISTORY,97 enabled = true,98}: UseDataGridUndoRedoProps<TData>): UseDataGridUndoRedoReturn<TData> {99 const propsRef = useAsRef({100 data,101// … truncated
What it pulls in
npm packages
