Skip to content

Understanding rendering

Learn what a render does, which cells it covers, what it costs, and when you need to call render() yourself.

Background

Handsontable does not keep every cell of your data set in the DOM. It renders only the part of the grid that you can see, plus a small buffer around it. As you scroll, it reuses the same DOM elements for new cells. This is called row virtualization and column virtualization, and it is what lets the grid hold hundreds of thousands of records without freezing the browser.

A render is the step that brings those DOM elements back in sync with your data and your configuration. Handsontable runs it for you at the right moments, so most applications never call render() at all. You need it when you change something that Handsontable cannot detect on its own — cell metadata, or a value outside the grid that one of your renderers reads.

How it works

What a render does

A render recalculates the grid’s layout, redraws its cells, and applies the result to the DOM. For every cell it draws, Handsontable:

  1. Resets the cell’s td element to a blank state.
  2. Resolves the cell’s metadata, which includes running the cells function if you defined one.
  3. Runs the cell’s renderer, which writes the value, the CSS classes, and any other markup.

A render redraws the grid in place. It does not change your data, your selection, or your scroll position.

What a render covers

A render covers the cells that are currently rendered, not your whole data set. That is the viewport plus the buffer set by viewportRowRenderingOffset and viewportColumnRenderingOffset.

So on a grid of 100,000 rows showing 30 of them, a render redraws those 30 rows and the buffer around them. It does not touch the other 99,970.

Two options change this. If you set renderAllRows or renderAllColumns to true, you turn virtualization off on that axis, every row or column lives in the DOM, and a render then covers all of them.

What a render costs

The cost of a render scales with the number of cells it draws, multiplied by the work your renderers and your cells function do for each of them. The number of records you hold is not what drives it.

This has two consequences:

  • A render on a grid with 1,000,000 records costs about the same as a render on a grid with 100 records, as long as both show the same number of cells. (Handsontable still keeps some per-record caches, such as row heights, so a render that has to rebuild one of them costs more.)
  • Work you put inside a renderer or inside the cells function runs again on every render, for every drawn cell. A small cost there adds up quickly. See Avoid the cells option when possible.

What makes an application slow is rarely one render. It is many renders in a row, which is what batching exists to prevent.

When Handsontable renders on its own

Handsontable picks the moments for you. It renders after every CRUD operation, while you scroll, when rows or columns are hidden or trimmed, and at the end of updateSettings(). Anything that changes the grid through the Handsontable API is covered.

When you call render() yourself

Call render() when you change something that Handsontable cannot detect. The common case is setCellMeta(), which writes to the cell’s metadata but does not repaint the grid:

hot.setCellMeta(0, 0, 'className', 'my-highlight');
hot.render(); // without this, the class is stored but not visible

Because setCellMeta() never renders on its own, several calls followed by one render() already cost exactly one render:

hot.setCellMeta(0, 0, 'className', 'my-highlight');
hot.setCellMeta(1, 0, 'readOnly', true);
hot.setCellMeta(2, 0, 'type', 'date');
hot.render(); // one render applies all three changes