Skip to content

Understanding data and indexes

Handsontable keeps two views of your data — the source data you provided and the visual dataset the user currently sees — and addresses each one with its own kind of index. Read this to understand which API methods operate on which view, and how features such as sorting, moving, hiding, and trimming affect them.

Background

When you pass a dataset to Handsontable, the grid doesn’t just display it — it keeps two related views of it:

  • The source data is the dataset you provided, in the order you provided it. It’s what you get back from getSourceData() and related methods, and it’s what you should persist.
  • The visual dataset is what the user currently sees, after any sorting, moving, hiding, or trimming has been applied. It’s what you get back from getData() and related methods.

Most of the time, these two views show the same rows in the same order, so the distinction doesn’t matter. It starts to matter as soon as a feature reorders or removes rows or columns for display purposes only, because at that point getData() and getSourceData() start to disagree.

How it works

Source data vs. visual data

Take a 4-row source dataset and sort it by the first column. The source array itself doesn’t change — Handsontable stores the new order separately — so reading the two views gives different results:

// source data, as provided
[
['Ford', 2018],
['Audi', 2020],
['BMW', 2019],
['Toyota', 2021],
]
hot.getSourceData();
// -> the original 4 rows, in the original order
hot.getData();
// -> the 4 rows sorted by year: Ford, BMW, Audi, Toyota

Use getSourceData() when you need the dataset as you provided it. Use getData() when you need what the user is currently looking at. The same split applies to the single-cell variants: getDataAtCell()/setDataAtCell() operate on the visual dataset, while getSourceDataAtCell()/setSourceDataAtCell() operate on the source data. For the full method list, see Binding to data: Related API reference.

Physical vs. visual indexes

Handsontable addresses these two views with two different kinds of row/column index:

  • A physical index is a position in the source data array. It doesn’t change when you sort, move, hide, or trim — only when you insert or remove a row or column.
  • A visual index is a position in what the user sees. It changes whenever sorting, moving, hiding, or trimming changes the display order.
TakesUse for
setDataAtCell(), getDataAtCell(), getDataAtRow(), getDataAtRowProp()Visual indexesReading or writing what the user sees
setSourceDataAtCell(), getSourceDataAtCell(), getSourceDataAtRow()Physical indexesReading or writing the underlying source data
The cells configuration functionPhysical row and columnPer-cell settings keyed to the source data
The cell configuration arrayVisual row and columnPer-cell settings keyed to the displayed position

To translate between the two, use toPhysicalRow(), toVisualRow(), toPhysicalColumn(), and toVisualColumn(). For example, after moving the row at visual position 0 to visual position 2:

Visual positionPhysical row
01
12
20
hot.toPhysicalRow(0); // -> 1, the row now shown first was originally row 1
hot.toVisualRow(0); // -> 2, the row originally first is now shown third

See Row moving: Set a pre-defined row order and Column moving: Set a pre-defined column order for worked examples of this mapping. Internally, Handsontable also tracks a third, renderable index for DOM rendering, but public API methods don’t take renderable indexes.

How features affect the two datasets

Different features remove or reorder rows and columns at different points between the source data and the DOM. This determines whether getData() still includes them:

Hidden rows/columnsTrimmed rows
In getSourceData()YesYes
In getData() (visual dataset)YesNo
Shifts other rows’/columns’ visual indexesNoYes
Rendered in the DOMNoNo

Hiding removes a row or column only from rendering — it stays in the visual dataset and keeps its visual index. Trimming removes it from the visual dataset entirely, so every row or column after it shifts to a lower visual index. See Row hiding and Row trimming for the plugin-level details.

Every accepted data change — even a single cell — makes Handsontable re-render all visible cells. When you apply many changes at once, wrap them in batch() so the grid renders only once.

Trade-offs

Operate on the source data when you’re persisting to a backend, exporting, or otherwise working with the dataset independent of how it’s currently displayed. Operate on the visual dataset when you’re reacting to what the user sees or clicked, such as inside a selection or change hook.

Handsontable binds to your source data by reference rather than copying it, so it’s possible to read or write that array directly instead of going through the API. Avoid this: direct writes bypass hooks, validators, and index mapping, so the grid’s visual dataset can drift out of sync with the array. See Binding to data: Understand binding as a reference for the recommended alternative.