Skip to content

Undo & Redo

WitTable implements undo/redo through a CommandManager that maintains a history stack. Every mutating operation (cell edit, resize, merge) is wrapped in a Command object with execute() and undo() methods. Press Ctrl+Z to undo and Ctrl+Y or Ctrl+Shift+Z to redo.

Live Demo
Double-click to edit cells. Use the buttons or Ctrl+Z / Ctrl+Y to undo and redo.
Edits: 0
View source code
UndoRedoDemo.tsx
import { useState, useRef, useCallback } from 'react';
import { WitTable } from '@witqq/spreadsheet-react';
import type { WitTableRef, CellChangeEvent } from '@witqq/spreadsheet-react';
import { DemoWrapper } from './DemoWrapper';
import { generateEmployees, employeeColumns } from './generate-data';
import { useSiteTheme } from './useSiteTheme';
const data = generateEmployees(50);
export function UndoRedoDemo() {
const { witTheme } = useSiteTheme();
const tableRef = useRef<WitTableRef>(null);
const [editCount, setEditCount] = useState(0);
const handleUndo = useCallback(() => tableRef.current?.undo(), []);
const handleRedo = useCallback(() => tableRef.current?.redo(), []);
const handleCellChange = useCallback((_event: CellChangeEvent) => {
setEditCount(prev => prev + 1);
}, []);
return (
<DemoWrapper title="Live Demo" description="Double-click to edit cells. Use the buttons or Ctrl+Z / Ctrl+Y to undo and redo." height={440}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div style={{ padding: '0.5rem 0.75rem', borderBottom: '1px solid #e2e8f0', flexShrink: 0, display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
<button onClick={handleUndo} style={{ padding: '4px 12px', cursor: 'pointer' }}>↩ Undo</button>
<button onClick={handleRedo} style={{ padding: '4px 12px', cursor: 'pointer' }}>↪ Redo</button>
<span style={{ fontSize: '0.8rem', color: '#64748b' }}>Edits: {editCount}</span>
</div>
<div style={{ flex: 1 }}>
<WitTable
theme={witTheme}
ref={tableRef}
columns={employeeColumns}
data={data}
showRowNumbers
editable
onCellChange={handleCellChange}
style={{ width: '100%', height: '100%' }}
/>
</div>
</div>
</DemoWrapper>
);
}

The history stack holds up to 100 commands by default. When the limit is reached, the oldest command is discarded.

CommandDescription
CellEditCommandSingle cell value change
BatchCellEditCommandMultiple cell changes (paste, cut, autofill)
ResizeColumnCommandColumn width change
ResizeRowCommandRow height change
MergeCellsCommandMerge a cell region
UnmergeCellsCommandUnmerge a cell region
InsertRowCommandInsert a row at a given index
DeleteRowCommandDelete a row at a given index

Expose undo and redo through toolbar buttons via ref:

import { useRef, useState } from 'react';
import { WitTable, WitTableRef } from '@witqq/spreadsheet-react';
function App() {
const ref = useRef<WitTableRef>(null);
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
return (
<>
<button
disabled={!canUndo}
onClick={() => ref.current?.getInstance().getCommandManager().undo()}
>
Undo
</button>
<button
disabled={!canRedo}
onClick={() => ref.current?.getInstance().getCommandManager().redo()}
>
Redo
</button>
<WitTable
ref={ref}
columns={columns}
data={data}
editable={true}
onCommandExecuted={() => {
setCanUndo(ref.current?.getInstance().getCommandManager().canUndo() ?? false);
setCanRedo(ref.current?.getInstance().getCommandManager().canRedo() ?? false);
}}
/>
</>
);
}
MethodSignatureDescription
execute(command: Command) => voidExecute and push to history
undo() => voidUndo last command
redo() => voidRedo last undone command
canUndo() => booleanWhether undo is available
canRedo() => booleanWhether redo is available
clear() => voidClear entire history
interface Command {
execute(): void;
undo(): void;
description: string;
}