Skip to content

Cell renderer

A cell renderer is a function that controls how cell content is displayed in the DOM. Override a built-in renderer or write your own to customize the visual output.

Use a cell renderer

You can use any of the built-in renderers by specifying their alias name in your column configuration. The example below shows how to use the numeric renderer, which formats numeric values according to the cell’s formatting options:

<HotTable
data={someData}
columns={[
{
renderer: "numeric",
},
]}
/>

Declare a custom renderer as a component

Handsontable’s React wrapper lets you create custom cell renderers using React components.

To use your component as a Handsontable renderer, pass it in the renderer prop if either HotTable or HotColumn components, as you would with any other config option.

JavaScript
import { HotTable, HotColumn } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
// your renderer component
const RendererComponent = (props) => {
// the available renderer-related props are:
// - `row` (row index)
// - `col` (column index)
// - `prop` (column property name)
// - `TD` (the HTML cell element)
// - `cellProperties` (the `cellProperties` object for the edited cell)
return (
<>
<i style={{ color: '#a9a9a9' }}>
Row: {props.row}, column: {props.col},
</i>{' '}
value: {props.value}
</>
);
};
const hotData = [
['A1', 'B1', 'C1', 'D1', 'E1'],
['A2', 'B2', 'C2', 'D2', 'E2'],
['A3', 'B3', 'C3', 'D3', 'E3'],
['A4', 'B4', 'C4', 'D4', 'E4'],
['A5', 'B5', 'C5', 'D5', 'E5'],
['A6', 'B6', 'C6', 'D6', 'E6'],
['A7', 'B7', 'C7', 'D7', 'E7'],
['A8', 'B8', 'C8', 'D8', 'E8'],
['A9', 'B9', 'C9', 'D9', 'E9'],
];
const ExampleComponent = () => {
return (
<HotTable
data={hotData}
autoWrapRow={true}
autoWrapCol={true}
autoRowSize={false}
autoColumnSize={false}
height="auto"
licenseKey="non-commercial-and-evaluation"
>
<HotColumn width={250} renderer={RendererComponent} />
</HotTable>
);
};
export default ExampleComponent;
TypeScript
import { HotTable, HotColumn } from '@handsontable/react-wrapper';
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
type RendererProps = {
TD?: HTMLTableCellElement;
value?: string | number;
row?: number;
col?: number;
cellProperties?: Handsontable.CellProperties;
};
// your renderer component
const RendererComponent = (props: RendererProps) => {
// the available renderer-related props are:
// - `row` (row index)
// - `col` (column index)
// - `prop` (column property name)
// - `TD` (the HTML cell element)
// - `cellProperties` (the `cellProperties` object for the edited cell)
return (
<>
<i style={{ color: '#a9a9a9' }}>
Row: {props.row}, column: {props.col},
</i>{' '}
value: {props.value}
</>
);
};
const hotData = [
['A1', 'B1', 'C1', 'D1', 'E1'],
['A2', 'B2', 'C2', 'D2', 'E2'],
['A3', 'B3', 'C3', 'D3', 'E3'],
['A4', 'B4', 'C4', 'D4', 'E4'],
['A5', 'B5', 'C5', 'D5', 'E5'],
['A6', 'B6', 'C6', 'D6', 'E6'],
['A7', 'B7', 'C7', 'D7', 'E7'],
['A8', 'B8', 'C8', 'D8', 'E8'],
['A9', 'B9', 'C9', 'D9', 'E9'],
];
const ExampleComponent = () => {
return (
<HotTable
data={hotData}
autoWrapRow={true}
autoWrapCol={true}
autoRowSize={false}
autoColumnSize={false}
height="auto"
licenseKey="non-commercial-and-evaluation"
>
<HotColumn width={250} renderer={RendererComponent} />
</HotTable>
);
};
export default ExampleComponent;

Use the renderer component within React’s context

In this example, React’s Context passes information available in the main app component to the renderer. In this case, we’re using just the renderer, but the same principle works with editors as well.

JavaScript
import { useState, useContext, createContext } from 'react';
import { HotTable, HotColumn } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
// a component
const HighlightContext = createContext(false);
// a renderer component
function CustomRenderer(props) {
const darkMode = useContext(HighlightContext);
if (!props.TD) return;
if (darkMode) {
props.TD.className = 'dark';
} else {
props.TD.className = '';
}
return <div>{props.value}</div>;
}
const ExampleComponent = () => {
const [darkMode, setDarkMode] = useState(false);
const toggleDarkMode = (event) => {
setDarkMode(event.target.checked);
};
return (
<HighlightContext.Provider value={darkMode}>
<div className="example-controls-container">
<div className="controls">
<label>
<input type="checkbox" onClick={toggleDarkMode} /> Dark mode
</label>
</div>
</div>
<HotTable
data={[['A1'], ['A2'], ['A3'], ['A4'], ['A5'], ['A6'], ['A7'], ['A8'], ['A9'], ['A10']]}
rowHeaders={true}
autoRowSize={false}
autoColumnSize={false}
height="auto"
licenseKey={'non-commercial-and-evaluation'}
>
<HotColumn renderer={CustomRenderer} />
</HotTable>
</HighlightContext.Provider>
);
};
export default ExampleComponent;
TypeScript
import { useState, useContext, MouseEvent, createContext } from 'react';
import { HotTable, HotColumn } from '@handsontable/react-wrapper';
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// Register all Handsontable's modules.
registerAllModules();
type RendererProps = {
TD?: HTMLTableCellElement;
value?: string | number;
row?: number;
col?: number;
cellProperties?: Handsontable.CellProperties;
};
// a component
const HighlightContext = createContext(false);
// a renderer component
function CustomRenderer(props: RendererProps) {
const darkMode = useContext(HighlightContext);
if (!props.TD) return;
if (darkMode) {
props.TD.className = 'dark';
} else {
props.TD.className = '';
}
return <div>{props.value}</div>;
}
const ExampleComponent = () => {
const [darkMode, setDarkMode] = useState(false);
const toggleDarkMode = (event: MouseEvent) => {
setDarkMode((event.target as HTMLInputElement).checked);
};
return (
<HighlightContext.Provider value={darkMode}>
<div className="example-controls-container">
<div className="controls">
<label>
<input type="checkbox" onClick={toggleDarkMode} /> Dark mode
</label>
</div>
</div>
<HotTable
data={[['A1'], ['A2'], ['A3'], ['A4'], ['A5'], ['A6'], ['A7'], ['A8'], ['A9'], ['A10']]}
rowHeaders={true}
autoRowSize={false}
autoColumnSize={false}
height="auto"
licenseKey={'non-commercial-and-evaluation'}
>
<HotColumn renderer={CustomRenderer} />
</HotTable>
</HighlightContext.Provider>
);
};
export default ExampleComponent;
CSS
#example2 .handsontable td.dark {
background: #000 !important;
color: #fff !important;
}
#example2 .handsontable td {
background: #fff !important;
color: #000 !important;
}

Declare a custom renderer as a function

You can also declare a custom renderer for the HotTable component by declaring it as a function. In the simplest scenario, you can pass the rendering function as the hotRenderer prop into HotTable or HotColumn. If you need the renderer to be a part of a columns config array, declare it under the renderer key.

The following example implements @handsontable/react-wrapper with a custom renderer added to one of the columns. It takes an image URL as the input and renders the image in the edited cell.

JavaScript
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
return (
<HotTable
id="hot"
data={[
['A1', '/docs/img/examples/professional-javascript-developers-nicholas-zakas.jpg'],
['A2', '/docs/img/examples/javascript-the-good-parts.jpg'],
]}
columns={[
{},
{
renderer(instance, td, row, col, prop, value, cellProperties) {
const img = document.createElement('img');
img.src = value;
img.addEventListener('mousedown', (event) => {
event.preventDefault();
});
td.innerText = '';
td.appendChild(img);
return td;
},
},
]}
colHeaders={true}
rowHeights={55}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
);
};
export default ExampleComponent;
TypeScript
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
return (
<HotTable
id="hot"
data={[
['A1', '/docs/img/examples/professional-javascript-developers-nicholas-zakas.jpg'],
['A2', '/docs/img/examples/javascript-the-good-parts.jpg'],
]}
columns={[
{},
{
renderer(instance, td, row, col, prop, value, cellProperties) {
const img = document.createElement('img');
img.src = value;
img.addEventListener('mousedown', (event) => {
event.preventDefault();
});
td.innerText = '';
td.appendChild(img);
return td;
},
},
]}
colHeaders={true}
rowHeights={55}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
);
};
export default ExampleComponent;

Register custom cell renderer

To register your own alias use registerRenderer() function from the handsontable/renderers module. It takes two arguments:

  • rendererName - a string representing a renderer function
  • renderer - a renderer function that will be represented by rendererName

If you’d like to register asteriskDecoratorRenderer under alias asterisk you have to call:

import { registerRenderer } from "handsontable/renderers";
registerRenderer("asterisk", asteriskDecoratorRenderer);

Choose aliases wisely. If you register your renderer under name that is already registered, the target function will be overwritten:

import { registerRenderer } from "handsontable/renderers";
registerRenderer("text", asteriskDecoratorRenderer);

Now "text" alias points to asteriskDecoratorRenderer function, not the built-in textRenderer.

So, unless you intentionally want to overwrite an existing alias, try to choose a unique name. A good practice is prefixing your aliases with some custom name (for example your GitHub username) to minimize the possibility of name collisions. This is especially important if you want to publish your renderer, because you never know aliases has been registered by the user who uses your renderer.

import { registerRenderer } from "handsontable/renderers";
registerRenderer("asterisk", asteriskDecoratorRenderer);

Someone might already registered such alias

import { registerRenderer } from "handsontable/renderers";
registerRenderer("my.asterisk", asteriskDecoratorRenderer);

That’s better.

Use an alias

The final touch is to use registered aliases. That way users can easily refer to an alias without the need to know the name of the function.

To sum up, a well prepared renderer function should look like this:

import { registerRenderer } from "handsontable/renderers";
function customRenderer(
hotInstance,
td,
row,
column,
prop,
value,
cellProperties
) {
// ...your custom logic of the renderer
}
// Register an alias
registerRenderer("my.custom", customRenderer);

From now on, you can use customRenderer like so:

<HotTable
data={someData}
columns={[
{
renderer: "my.custom",
},
]}
/>

When your custom renderer should preserve the default text output, call the built-in textRenderer() first. See Extend a built-in renderer.

Extend a built-in renderer

When you build on top of a built-in renderer such as textRenderer or htmlRenderer, Handsontable doesn’t call it for you. You call it inside your custom renderer, before your extra logic.

Use textRenderer when you want plain-text output and then apply styling or additional DOM changes.

Use htmlRenderer when your output is trusted HTML and you intentionally render with innerHTML.

Skip the built-in renderer when your renderer fully controls cell output from scratch, for example the image-based coverRenderer in Render custom HTML in cells.

Both of the following call styles are valid:

// Legacy style, common in classic JavaScript examples.
textRenderer.apply(this, arguments);
// Direct invocation style, common in ESM and TypeScript examples.
textRenderer(instance, td, row, column, prop, value, cellProperties);

Handsontable runs baseRenderer for you

baseRenderer is a separate renderer, and it isn’t one of the renderers described above. It adds the cell’s class names and ARIA attributes, including className, readOnly, and the invalid-cell class.

Since version 17.0.0, Handsontable runs baseRenderer for you. It runs after your custom renderer, whenever your renderer didn’t run it. Your cells keep their class names even when your renderer calls no built-in renderer at all. Before version 17.0.0, such cells received no class names.

Call baseRenderer yourself when you need it to run before your changes — for example, when your renderer sets a class that baseRenderer also manages, such as the invalid-cell class. A baseRenderer that runs last removes that class.

Render custom HTML in cells

This example shows how to use custom cell renderers to display HTML content in a cell. This is a very powerful feature. Just remember to escape any HTML code that could be used for XSS attacks.

In the below configuration:

  • Title column uses built-in HTML renderer that allows any HTML. This is unsafe if your data comes from an untrusted source. A Handsontable user can enter <script> or other potentially malicious tags using the cell editor.
  • Description column also uses HTML renderer (same as above)
  • Comments column uses a custom renderer (safeHtmlRenderer). This should be safe for user input, because only certain tags are allowed
  • Cover column accepts image URL as a string and converts it to a <img> in the renderer
JavaScript
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const data = [
{
title:
'<a href="https://www.amazon.com/Professional-JavaScript-Developers-Nicholas-Zakas/dp/1118026691">Professional JavaScript for Web Developers</a>',
description:
'This <a href="https://www.amazon.com/Professional-JavaScript-Developers-Nicholas-Zakas/dp/1118026691">book</a> provides a developer-level introduction along with more advanced and useful features of <b>JavaScript</b>.',
comments: 'I would rate it ★★★★☆',
cover: '/docs/img/examples/professional-javascript-developers-nicholas-zakas.jpg',
},
{
title: '<a href="https://shop.oreilly.com/product/9780596517748.do">JavaScript: The Good Parts</a>',
description:
'This book provides a developer-level introduction along with <b>more advanced</b> and useful features of JavaScript.',
comments: 'This is the book about JavaScript',
cover: '/docs/img/examples/javascript-the-good-parts.jpg',
},
{
title: '<a href="https://shop.oreilly.com/product/9780596805531.do">JavaScript: The Definitive Guide</a>',
description:
'<em>JavaScript: The Definitive Guide</em> provides a thorough description of the core <b>JavaScript</b> language and both the legacy and standard DOMs implemented in web browsers.',
comments:
'I\'ve never actually read it, but the <a href="https://shop.oreilly.com/product/9780596805531.do">comments</a> are highly <strong>positive</strong>.',
cover: '/docs/img/examples/javascript-the-definitive-guide.jpg',
},
];
function safeHtmlRenderer(_instance, td, _row, _col, _prop, value, _cellProperties) {
// WARNING: Be sure you only allow certain HTML tags to avoid XSS threats.
// Sanitize the "value" before passing it to the innerHTML property.
td.innerHTML = value;
}
function coverRenderer(_instance, td, _row, _col, _prop, value, _cellProperties) {
const img = document.createElement('img');
img.src = value;
img.addEventListener('mousedown', (event) => {
event.preventDefault();
});
td.innerText = '';
td.appendChild(img);
return td;
}
return (
<HotTable
data={data}
colWidths={[200, 200, 200, 80]}
colHeaders={['Title', 'Description', 'Comments', 'Cover']}
height="auto"
columns={[
{ data: 'title', renderer: 'html' },
{ data: 'description', renderer: 'html' },
{ data: 'comments', renderer: safeHtmlRenderer },
{ data: 'cover', renderer: coverRenderer },
]}
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
);
};
export default ExampleComponent;
TypeScript
import { HotTable } from '@handsontable/react-wrapper';
import Handsontable from 'handsontable/base';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
const ExampleComponent = () => {
const data = [
{
title:
'<a href="https://www.amazon.com/Professional-JavaScript-Developers-Nicholas-Zakas/dp/1118026691">Professional JavaScript for Web Developers</a>',
description:
'This <a href="https://www.amazon.com/Professional-JavaScript-Developers-Nicholas-Zakas/dp/1118026691">book</a> provides a developer-level introduction along with more advanced and useful features of <b>JavaScript</b>.',
comments: 'I would rate it ★★★★☆',
cover: '/docs/img/examples/professional-javascript-developers-nicholas-zakas.jpg',
},
{
title: '<a href="https://shop.oreilly.com/product/9780596517748.do">JavaScript: The Good Parts</a>',
description:
'This book provides a developer-level introduction along with <b>more advanced</b> and useful features of JavaScript.',
comments: 'This is the book about JavaScript',
cover: '/docs/img/examples/javascript-the-good-parts.jpg',
},
{
title: '<a href="https://shop.oreilly.com/product/9780596805531.do">JavaScript: The Definitive Guide</a>',
description:
'<em>JavaScript: The Definitive Guide</em> provides a thorough description of the core <b>JavaScript</b> language and both the legacy and standard DOMs implemented in web browsers.',
comments:
'I\'ve never actually read it, but the <a href="https://shop.oreilly.com/product/9780596805531.do">comments</a> are highly <strong>positive</strong>.',
cover: '/docs/img/examples/javascript-the-definitive-guide.jpg',
},
];
function safeHtmlRenderer(
_instance: Handsontable,
td: HTMLTableCellElement,
_row: number,
_col: number,
_prop: string | number,
value: Handsontable.CellValue,
_cellProperties: Handsontable.CellProperties
) {
// WARNING: Be sure you only allow certain HTML tags to avoid XSS threats.
// Sanitize the "value" before passing it to the innerHTML property.
td.innerHTML = value;
}
function coverRenderer(
_instance: Handsontable,
td: HTMLTableCellElement,
_row: number,
_col: number,
_prop: string | number,
value: Handsontable.CellValue,
_cellProperties: Handsontable.CellProperties
) {
const img = document.createElement('img');
img.src = value;
img.addEventListener('mousedown', (event) => {
event.preventDefault();
});
td.innerText = '';
td.appendChild(img);
return td;
}
return (
<HotTable
data={data}
colWidths={[200, 200, 200, 80]}
colHeaders={['Title', 'Description', 'Comments', 'Cover']}
height="auto"
columns={[
{ data: 'title', renderer: 'html' },
{ data: 'description', renderer: 'html' },
{ data: 'comments', renderer: safeHtmlRenderer },
{ data: 'cover', renderer: coverRenderer },
]}
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
);
};
export default ExampleComponent;

A common use of a custom renderer is to turn a cell value into a clickable hyperlink. The renderer reads the cell value, builds an anchor (<a>) element, and appends it to the cell’s DOM node.

function hyperlinkRenderer(instance, td, row, column, prop, value, cellProperties) {
Handsontable.dom.empty(td);
const link = document.createElement('a');
link.href = value;
link.textContent = value;
link.target = '_blank';
link.rel = 'noopener noreferrer';
td.appendChild(link);
return td;
}

Assign the renderer to a column through the renderer option, or register it by alias with registerRenderer() as shown in Register custom cell renderer.

Render custom HTML in header

You can also put HTML into row and column headers. If you need to attach events to DOM elements like the checkbox below, just remember to identify the element by class name, not by id. This is because row and column headers are duplicated in the DOM tree and id attribute must be unique.

If your goal is to extend a built-in renderer before adding custom logic, see Extend a built-in renderer.

JavaScript
import { useRef } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
import { textRenderer } from 'handsontable/renderers/textRenderer';
// register Handsontable's modules
registerAllModules();
const ALLOWED_TAGS = ['B', 'EM', 'INPUT', 'BR', 'TABLE', 'THEAD', 'TBODY', 'TR', 'TD', 'TH'];
const ALLOWED_ATTRIBUTES = ['type', 'class', 'checked', 'colspan', 'rowspan'];
const DROPPED_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE'];
// Handsontable has no built-in sanitizer since v18.0, and `sanitizer` is grid-level:
// it also filters pasted HTML, so the table tags have to survive -- otherwise pasting
// a range degrades to plain text. In production, use a vetted library such as DOMPurify.
// See https://handsontable.com/docs/security/
const sanitizeHeader = (html) => {
const template = document.createElement('template');
template.innerHTML = html;
template.content.querySelectorAll('*').forEach((element) => {
if (DROPPED_TAGS.includes(element.tagName)) {
// Unwrapping these would promote their source text into the output
element.remove();
} else if (ALLOWED_TAGS.includes(element.tagName)) {
Array.from(element.attributes).forEach((attribute) => {
if (!ALLOWED_ATTRIBUTES.includes(attribute.name)) {
element.removeAttribute(attribute.name);
}
});
} else {
// Unwrap a disallowed element, keeping its text content
element.replaceWith(...Array.from(element.childNodes));
}
});
return template.innerHTML;
};
const ExampleComponent = () => {
const hotRef = useRef(null);
let isChecked = false;
function customRenderer(_instance, td) {
textRenderer.apply(this, arguments);
if (isChecked) {
td.style.backgroundColor = 'yellow';
} else {
td.style.backgroundColor = 'rgba(255,255,255,0.1)';
}
}
const exampleContainerMouseupCallback = (event) => {
const hot = hotRef.current?.hotInstance;
if (event.target.nodeName == 'INPUT' && event.target.className == 'checker') {
isChecked = !event.target.checked;
hot?.render();
}
};
return (
<div id="exampleContainer5" onMouseUp={(...args) => exampleContainerMouseupCallback(...args)}>
<HotTable
ref={hotRef}
height="auto"
columns={[{}, { renderer: customRenderer }]}
colHeaders={function (col) {
switch (col) {
case 0:
return '<b>Bold</b> and <em>Beautiful</em>';
case 1:
return `Some <input type="checkbox" class="checker" ${isChecked ? `checked="checked"` : ''}> checkbox`;
default:
return '';
}
}}
autoWrapRow={true}
autoWrapCol={true}
sanitizer={sanitizeHeader}
licenseKey="non-commercial-and-evaluation"
/>
</div>
);
};
export default ExampleComponent;
TypeScript
import { useRef, MouseEvent } from 'react';
import Handsontable from 'handsontable/base';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
import { textRenderer } from 'handsontable/renderers/textRenderer';
// register Handsontable's modules
registerAllModules();
const ALLOWED_TAGS = ['B', 'EM', 'INPUT', 'BR', 'TABLE', 'THEAD', 'TBODY', 'TR', 'TD', 'TH'];
const ALLOWED_ATTRIBUTES = ['type', 'class', 'checked', 'colspan', 'rowspan'];
const DROPPED_TAGS = ['SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE'];
// Handsontable has no built-in sanitizer since v18.0, and `sanitizer` is grid-level:
// it also filters pasted HTML, so the table tags have to survive -- otherwise pasting
// a range degrades to plain text. In production, use a vetted library such as DOMPurify.
// See https://handsontable.com/docs/security/
const sanitizeHeader = (html: string): string => {
const template = document.createElement('template');
template.innerHTML = html;
template.content.querySelectorAll('*').forEach((element) => {
if (DROPPED_TAGS.includes(element.tagName)) {
// Unwrapping these would promote their source text into the output
element.remove();
} else if (ALLOWED_TAGS.includes(element.tagName)) {
Array.from(element.attributes).forEach((attribute) => {
if (!ALLOWED_ATTRIBUTES.includes(attribute.name)) {
element.removeAttribute(attribute.name);
}
});
} else {
// Unwrap a disallowed element, keeping its text content
element.replaceWith(...Array.from(element.childNodes));
}
});
return template.innerHTML;
};
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
let isChecked = false;
function customRenderer(this: Handsontable, _instance: Handsontable, td: HTMLTableCellElement) {
textRenderer.apply(this, arguments as any);
if (isChecked) {
td.style.backgroundColor = 'yellow';
} else {
td.style.backgroundColor = 'rgba(255,255,255,0.1)';
}
}
const exampleContainerMouseupCallback = (event: MouseEvent) => {
const hot = hotRef.current?.hotInstance;
if (
(event.target as HTMLInputElement).nodeName == 'INPUT' &&
(event.target as HTMLInputElement).className == 'checker'
) {
isChecked = !(event.target as HTMLInputElement).checked;
hot?.render();
}
};
return (
<div id="exampleContainer5" onMouseUp={(...args) => exampleContainerMouseupCallback(...args)}>
<HotTable
ref={hotRef}
height="auto"
columns={[{}, { renderer: customRenderer }]}
colHeaders={function (col: number) {
switch (col) {
case 0:
return '<b>Bold</b> and <em>Beautiful</em>';
case 1:
return `Some <input type="checkbox" class="checker" ${isChecked ? `checked="checked"` : ''}> checkbox`;
default:
return '';
}
}}
autoWrapRow={true}
autoWrapCol={true}
sanitizer={sanitizeHeader}
licenseKey="non-commercial-and-evaluation"
/>
</div>
);
};
export default ExampleComponent;

Add event listeners in cell renderer function

If you are writing an advanced cell renderer, and you want to add some custom behavior after a certain user action (i.e. after user hover a mouse pointer over a cell) you might be tempted to add an event listener directly to table cell node passed as an argument to the renderer function. Unfortunately, this will almost always cause you trouble and you will end up with either performance issues or having the listeners attached to the wrong cell.

This is because Handsontable:

  • Calls renderer functions multiple times per cell - this can lead to having multiple copies of the same event listener attached to a cell
  • Reuses table cell nodes during table scrolling and adding/removing new rows/columns - this can lead to having event listeners attached to the wrong cell

Before deciding to attach an event listener in cell renderer make sure, that there is no Handsontable event that suits your needs. Using Handsontable events system is the safest way to respond to user actions.

If you did’t find a suitable Handsontable event put the cell content into a wrapping <div>, attach the event listener to the wrapper and then put it into the table cell.

Changes made outside a renderer do not survive

Handsontable resets a cell’s td element before it runs a renderer, so only what a renderer writes back survives. Understanding rendering lists exactly what the reset clears.