Skip to content

Change Tracking

ChangeTracker monitors cell modifications and maintains a status lifecycle for each changed cell. This enables visual indicators (colored cell borders/backgrounds) that show users which cells have been modified, are being saved, or encountered errors.

  • undefined — Cell has not been modified since baseline
  • changed — Cell value differs from baseline
  • saving — Save operation in progress
  • saved — Successfully persisted (auto-clears after short delay)
  • error — Save operation failed
Live Demo
Edit cells to see 'changed' status (blue border). Click Save to see saving → saved lifecycle.
Edit cells, then click "Simulate Save" to see the status lifecycle.
View source code
ChangeTrackingDemo.tsx
import { useRef, useEffect, useState } from 'react';
import { Spreadsheet } from '@witqq/spreadsheet-react';
import type { SpreadsheetRef } from '@witqq/spreadsheet-react';
import { DemoWrapper } from './DemoWrapper';
import { DemoButton } from './DemoButton';
import { DemoToolbar, StatusText } from './DemoToolbar';
import { generateEmployees, employeeColumns } from './generate-data';
import { useSiteTheme } from './useSiteTheme';
const data = generateEmployees(30);
export function ChangeTrackingDemo() {
const { witTheme } = useSiteTheme();
const tableRef = useRef<SpreadsheetRef>(null);
const [status, setStatus] = useState('Edit cells, then click "Simulate Save" to see the status lifecycle.');
useEffect(() => {
const engine = tableRef.current?.getInstance();
if (!engine) return;
engine.getChangeTracker().captureBaseline();
}, []);
const handleSimulateSave = async () => {
const engine = tableRef.current?.getInstance();
if (!engine) return;
const tracker = engine.getChangeTracker();
const changed = tracker.getChangedCells();
if (changed.length === 0) {
setStatus('No changes to save. Edit some cells first.');
return;
}
setStatus(`Saving ${changed.length} cell(s)...`);
for (const cell of changed) {
tracker.setCellStatus(cell.row, cell.col, 'saving');
}
engine.requestRender();
await new Promise(r => setTimeout(r, 1000));
for (const cell of changed) {
tracker.setCellStatus(cell.row, cell.col, 'saved');
}
engine.requestRender();
setStatus(`Saved ${changed.length} cell(s). Status will clear shortly.`);
await new Promise(r => setTimeout(r, 1500));
for (const cell of changed) {
engine.getCellStore().clearMetadata(cell.row, cell.col);
}
tracker.captureBaseline();
engine.requestRender();
setStatus('All changes saved and cleared. Edit more cells to try again.');
};
return (
<DemoWrapper title="Live Demo" description="Edit cells to see 'changed' status (blue border). Click Save to see saving → saved lifecycle." height={440}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<DemoToolbar>
<DemoButton onClick={handleSimulateSave}>💾 Simulate Save</DemoButton>
<StatusText>{status}</StatusText>
</DemoToolbar>
<div style={{ flex: 1 }}>
<Spreadsheet
theme={witTheme}
ref={tableRef}
columns={employeeColumns}
data={data}
showRowNumbers
editable
style={{ width: '100%', height: '100%' }}
/>
</div>
</div>
</DemoWrapper>
);
}
import { useRef } from 'react';
import { Spreadsheet, SpreadsheetRef } from '@witqq/spreadsheet-react';
function App() {
const ref = useRef<SpreadsheetRef>(null);
const handleSave = async () => {
const tracker = ref.current?.getInstance().getChangeTracker();
const changed = tracker?.getChangedCells() ?? [];
for (const cell of changed) {
tracker?.setCellStatus(cell.row, cell.col, 'saving');
}
try {
await saveToBackend(changed);
for (const cell of changed) {
tracker?.setCellStatus(cell.row, cell.col, 'saved');
}
} catch (err) {
for (const cell of changed) {
tracker?.setCellStatus(cell.row, cell.col, 'error');
}
}
};
return (
<>
<button onClick={() => ref.current?.getInstance().getChangeTracker().captureBaseline()}>
Capture Baseline
</button>
<button onClick={handleSave}>Save Changes</button>
<Spreadsheet ref={ref} columns={columns} data={data} editable={true} />
</>
);
}

Use setCellStatus to integrate with your backend save mechanism:

// Mark cells as saving
tracker.setCellStatus(row, col, 'saving');
// On success
tracker.setCellStatus(row, col, 'saved');
// On failure
tracker.setCellStatus(row, col, 'error');
MethodSignatureDescription
captureBaseline() => voidSnapshot current state as baseline
setCellStatus(row: number, col: number, status: CellStatus) => voidSet cell status
getCellStatus(row: number, col: number) => CellStatus | undefinedGet cell status
getChangedCells() => Array<{ row: number; col: number }>List all modified cells
clearChanges() => voidClear all tracking data