Batch Editing: Row Data
Introduction
Section titled “Introduction”The coarsest way to change what the grid shows is to hand it a new row-data array. Instead of editing cell by cell, you replace the dataset in one operation — the grid re-runs its sort and filter against the fresh rows and repaints. This is the right tool when your data arrives in bulk: an initial load, a page of server results, a “refresh”, or an in-memory transform of every row.
Two layers cooperate:
- Core (community):
grid.setData(rows)swaps the entire dataset in a single batch. It’s the primitive every update builds on — filters and sort survive the swap, but because selection is stored by position, a full replace leaves the selection sitting on whatever record now occupies those rows. To keep it pinned yourself, the newgrid.selectionAPI lets you read the selection before the swap and restore it after. - Enterprise:
batchRowData()wrapssetDataso every swap becomes an identity-aware delta: it diffs the old rows against the new by agetRowId, keeps the selection pinned to the same records as they move, holds the scroll position, and hands you a summary of what changed.
import { batchRowData } from '@zengrid/enterprise';
const batch = batchRowData(grid, { getRowId: (row) => row[0] });
grid.setData(nextRows); // selection + scroll preserved by identitybatch.getLastDelta(); // { added, removed, updated, moved, unchanged, … }setData — the community batch
Section titled “setData — the community batch”grid.setData(rows) is a whole-array replace, not a merge: pass the complete
next state of the grid and it becomes the source of truth. It runs synchronously,
re-applies the active sort and filter, and repaints once — so replacing a
thousand rows is one operation, not a thousand.
Selection is stored in source (data) coordinates so it survives sort and
filter, but a full replace reshuffles those positions. The core
grid.selection facade (read getRanges() / getActive(), then
selectRows / selectCell / setActive to restore) is all you need to pin it
by hand — and it’s exactly what the enterprise helper automates.
batchRowData() — identity-aware updates
Section titled “batchRowData() — identity-aware updates”Give batchRowData a getRowId and each setData becomes an immutable-data
style update:
| Config | Effect |
|---|---|
getRowId(row, index) |
Stable identity per row. Unlocks tracking a record across reorders, inserts, and deletes. Defaults to the row’s index (positional). |
preserveSelection |
Keep the selection pinned to the same records across the swap. Default true. |
preserveScroll |
Restore the scroll position after the swap. Default true. |
changed(prev, next) |
Decide whether two cell values differ (drives updated). Default !Object.is. |
onDataChanged(delta) |
Called after every swap with the computed delta. |
It returns a handle: apply(rows) (swap and get the delta back), getLastDelta(),
and destroy() (restore the plain setData).
The delta
Section titled “The delta”Every swap produces a BatchRowDataDelta describing the diff by identity:
| Field | Meaning |
|---|---|
added / addedIds |
Rows whose id is new. |
removed / removedIds |
Rows whose id disappeared. |
updated / updatedRows |
Rows present in both whose values changed (new source indices). |
moved |
Rows present in both that landed at a different index. |
unchanged |
Rows at the same index with identical values. |
Flashing the changed cells is a separate concern — compose
changeHighlight() alongside
batchRowData() and updated cells flash on each swap.
Try it live
Section titled “Try it live”The community baseline. Click a row to select it, then 'Reverse order' — setData swaps the whole dataset and the selection stays on the same POSITION (row 1), now a different company. This is why a raw replace loses your selection. The next tab fixes it.
- Single Row / Cell — update one row or one cell in place instead of replacing the whole array.
- Transactions — add, remove, and update rows with a transaction object.
- Highlighting Changes — flash the cells
a
batchRowDataswap changed.