Skip to content

Batch Editing: Row Data

Enterprise

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 new grid.selection API lets you read the selection before the swap and restore it after.
  • Enterprise: batchRowData() wraps setData so every swap becomes an identity-aware delta: it diffs the old rows against the new by a getRowId, 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 identity
batch.getLastDelta(); // { added, removed, updated, moved, unchanged, … }

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.

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).

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.

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.