Searching values
Searching values
Search data across Handsontable using the built-in API methods of the Search plugin, and implement your own search UI.
Overview
The Search plugin lets you scan all cells in the grid and get back a list of matches. Enable it by setting the search option to true or to a configuration object.
Once enabled, the plugin exposes the query(queryStr) method. Call it with a search string whenever the user types. By default, the search is case-insensitive and matches partial cell values.
How query() works
Calling query(queryStr, [callback], [queryMethod]) does two things:
- Iterates over every cell in the grid and tests each one using the
queryMethod. - After each test, calls the
callbackto update cell metadata (isSearchResult).
It returns an array of result objects - one for each matching cell:
| Property | Type | Description |
|---|---|---|
row | number | Visual row index of the matching cell |
col | number | Visual column index of the matching cell |
data | string|number|null | Value of the matching cell |
After calling query(), call hot.render() to refresh visual highlighting.
Search result class
After query() runs, every cell where isSearchResult === true automatically receives the CSS class htSearchResult. You can replace this class in two ways:
- At initialization: set
search: { searchResultClass: 'my-class' } - Programmatically: call
hot.getPlugin('search').setSearchResultClass('my-class')
Custom queryMethod
The queryMethod function determines whether the query string matches a cell value. It is called once per cell during every query() call.
Signature:
function queryMethod(query, value, cellProperties) { // return true for a match, false otherwise}| Parameter | Type | Description |
|---|---|---|
query | string | The search string passed to query() |
value | string|number|null | The cell value (from getDataAtCell()) |
cellProperties | object | The cell’s metadata object (includes locale, type, and other cell options) |
The built-in default performs a case-insensitive, locale-aware substring match:
function defaultQueryMethod(query, value, cellProperties) { if (query === undefined || query === null || query.length === 0) { return false; } if (value === undefined || value === null) { return false; }
return value.toString().toLocaleLowerCase(cellProperties.locale) .indexOf(query.toLocaleLowerCase(cellProperties.locale)) !== -1;}You can set a custom query method in three ways:
- At initialization:
search: { queryMethod: myQueryMethod } - Programmatically:
hot.getPlugin('search').setQueryMethod(myQueryMethod) - Per
query()call:searchPlugin.query(queryStr, callback, myQueryMethod)(applies to that call only)
Custom result callback
The callback function is called for every cell during a query() run, whether or not the cell matches. It is responsible for updating cell metadata so the renderer knows which cells to highlight.
Signature:
function callback(instance, row, col, data, testResult) { // update cell metadata based on testResult}| Parameter | Type | Description |
|---|---|---|
instance | Handsontable | The Handsontable instance |
row | number | Visual row index |
col | number | Visual column index |
data | string|number|null | The cell value |
testResult | boolean | true if the cell matches the query, false otherwise |
The built-in default sets the isSearchResult flag on each cell’s metadata:
function defaultCallback(instance, row, col, data, testResult) { instance.getCellMeta(row, col).isSearchResult = testResult;}If you override the callback to add custom logic (for example, to count results), call the default behavior manually so that cell highlighting still works.
You can set a custom callback in three ways:
- At initialization:
search: { callback: myCallback } - Programmatically:
hot.getPlugin('search').setCallback(myCallback) - Per
query()call:searchPlugin.query(queryStr, myCallback)(applies to that call only)
Per-cell queryMethod and callback
Both queryMethod and callback can be overridden for individual cells, columns, or rows using Handsontable’s cascading configuration model. Set a search object directly in a cell, columns, or rows entry:
handsontable({ data: myData, search: true, columns: [ {}, // Column 1: exact match only, everything else uses the global queryMethod { search: { queryMethod(queryStr, value) { return queryStr.toString() === value.toString(); } } } ]});You can also set it programmatically on a specific cell using setCellMeta():
hot.setCellMeta(row, col, 'search', { queryMethod(queryStr, value) { return queryStr.toString() === value.toString(); }});Per-cell settings take precedence over the plugin-level queryMethod and callback. Only queryMethod and callback support per-cell overrides - searchResultClass does not.
Simplest use case
The example below:
- Enables the
Searchplugin by settingsearchtotrue - Listens for
keyupevents on a search input - Calls
query()on each keystroke and re-renders the grid to apply highlighting
import { useRef, useCallback } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef(null); const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'], ];
const searchFieldKeyupCallback = useCallback( (event) => { const hot = hotRef.current?.hotInstance; // get the `Search` plugin's instance const search = hot?.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult); hot?.render(); }, [hotRef.current] );
return ( <> <div className="example-controls-container"> <div className="controls"> <input id="search_field" type="search" placeholder="Search" onKeyUp={(event) => searchFieldKeyupCallback(event)} /> </div> </div> <HotTable ref={hotRef} data={data} colHeaders={true} search={true} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef, useCallback } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null);
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'], ];
const searchFieldKeyupCallback = useCallback( (event: React.KeyboardEvent<HTMLInputElement>) => { const hot = hotRef.current?.hotInstance; // get the `Search` plugin's instance const search = hot?.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot?.render(); }, [hotRef.current] );
return ( <> <div className="example-controls-container"> <div className="controls"> <input id="search_field" type="search" placeholder="Search" onKeyUp={(event) => searchFieldKeyupCallback(event)} /> </div> </div> <HotTable ref={hotRef} data={data} colHeaders={true} search={true} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;Custom search result class
You can style search results with a custom CSS class, using the Search plugin’s searchResultClass option.
The example below highlights search results with a pink background and red text. To do this, it:
- Defines a custom CSS class called
my-class, scoped to.ht-theme-main .handsontable - Enables the
Searchplugin with a configuration object - Sets
searchResultClassto'my-class'
import { useRef, useCallback } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef(null); const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'], ];
const searchFieldKeyupCallback = useCallback( (event) => { const hot = hotRef.current?.hotInstance; const search = hot?.getPlugin('search'); const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult); hot?.render(); }, [hotRef.current] );
return ( <> <div className="example-controls-container"> <div className="controls"> <input id="search_field2" type="search" placeholder="Search" onKeyUp={(...args) => searchFieldKeyupCallback(...args)} /> </div> </div> <HotTable ref={hotRef} data={data} colHeaders={true} // enable the `Search` plugin search={{ // add your custom CSS class searchResultClass: 'my-class', }} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef, useCallback } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null);
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'], ];
const searchFieldKeyupCallback = useCallback( (event: React.KeyboardEvent<HTMLInputElement>) => { const hot = hotRef.current?.hotInstance; const search = hot?.getPlugin('search'); const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult); hot?.render(); }, [hotRef.current] );
return ( <> <div className="example-controls-container"> <div className="controls"> <input id="search_field2" type="search" placeholder="Search" onKeyUp={(...args) => searchFieldKeyupCallback(...args)} /> </div> </div> <HotTable ref={hotRef} data={data} colHeaders={true} // enable the `Search` plugin search={{ // add your custom CSS class searchResultClass: 'my-class', }} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;.ht-theme-main .handsontable .my-class { background: pink; color: red;}Custom query method
You can replace the built-in substring search with a custom query method, using the queryMethod option.
The example below searches only for exact matches. To do this, it:
- Defines a custom query method called
onlyExactMatchthat uses strict equality (===) - Enables the
Searchplugin with a configuration object - Sets
queryMethodtoonlyExactMatch
import { useRef, useCallback } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef(null); const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'], ];
const searchFieldKeyupCallback = useCallback( (event) => { const hot = hotRef.current?.hotInstance; const search = hot?.getPlugin('search'); // use the `Search`'s `query()` method const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult); hot?.render(); }, [hotRef.current] );
// define your custom query method function onlyExactMatch(queryStr, value) { return queryStr.toString() === value.toString(); }
return ( <> <div className="example-controls-container"> <div className="controls"> <input id="search_field3" type="search" placeholder="Search" onKeyUp={(...args) => searchFieldKeyupCallback(...args)} /> </div> </div> <HotTable ref={hotRef} data={data} colHeaders={true} // enable the `Search` plugin search={{ // add your custom query method queryMethod: onlyExactMatch, }} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef, useCallback } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hotRef = useRef<HotTableRef>(null);
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'], ];
const searchFieldKeyupCallback = useCallback( (event: React.KeyboardEvent<HTMLInputElement>) => { const hot = hotRef.current?.hotInstance; const search = hot?.getPlugin('search'); // use the `Search`'s `query()` method const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot?.render(); }, [hotRef.current] );
// define your custom query method function onlyExactMatch(queryStr: { toString: () => any }, value: { toString: () => any }) { return queryStr.toString() === value.toString(); }
return ( <> <div className="example-controls-container"> <div className="controls"> <input id="search_field3" type="search" placeholder="Search" onKeyUp={(...args) => searchFieldKeyupCallback(...args)} /> </div> </div> <HotTable ref={hotRef} data={data} colHeaders={true} // enable the `Search` plugin search={{ // add your custom query method queryMethod: onlyExactMatch, }} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;Custom callback
You can add a custom callback function, using the Search plugin’s callback option.
The example below displays the number of matching search results. To do this, it:
- Defines a custom callback function called
searchResultCounterthat counts matches and calls the default callback to preserve highlighting - Enables the
Searchplugin with a configuration object - Sets
callbacktosearchResultCounter
import { useRef, useState } from 'react';import { HotTable } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hot4Ref = useRef(null); const [resultCount, setResultCounter] = useState(0); const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'], ];
// define your custom callback function function searchResultCounter(_instance, _row, _col, _value, result) { const DEFAULT_CALLBACK = function (instance, row, col, _data, testResult) { instance.getCellMeta(row, col).isSearchResult = testResult; };
DEFAULT_CALLBACK.apply(this, arguments);
if (result) { setResultCounter((count) => count + 1); } }
const handleKeyUp = (event) => { setResultCounter(0);
const search = hot4Ref.current?.hotInstance?.getPlugin('search'); const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult); hot4Ref.current?.hotInstance?.render(); };
return ( <> <div className="example-controls-container"> <div className="controls"> <input id="search_field4" type="search" placeholder="Search" onKeyUp={handleKeyUp} /> </div> <output className="console" id="output"> {resultCount} results </output> </div> <HotTable ref={hot4Ref} data={data} colHeaders={true} // enable the `Search` plugin search={{ // add your custom callback function callback: searchResultCounter, }} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;import { useRef, useState } from 'react';import { HotTable, HotTableRef } from '@handsontable/react-wrapper';import { registerAllModules } from 'handsontable/registry';import Handsontable from 'handsontable/base';
// register Handsontable's modulesregisterAllModules();
const ExampleComponent = () => { const hot4Ref = useRef<HotTableRef>(null); const [resultCount, setResultCounter] = useState(0);
const data = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'white', 'gray'], ];
// define your custom callback function function searchResultCounter( this: Handsontable, _instance: Handsontable, _row: number, _col: number, _value: any, result: any ) { const DEFAULT_CALLBACK = function (instance: Handsontable, row: number, col: number, _data: any, testResult: any) { instance.getCellMeta(row, col).isSearchResult = testResult; };
DEFAULT_CALLBACK.apply(this, arguments as any);
if (result) { setResultCounter((count) => count + 1); } }
const handleKeyUp = (event: React.KeyboardEvent<HTMLInputElement>) => { setResultCounter(0);
const search = hot4Ref.current?.hotInstance?.getPlugin('search'); const queryResult = search?.query(event.currentTarget.value);
console.log(queryResult);
hot4Ref.current?.hotInstance?.render(); };
return ( <> <div className="example-controls-container"> <div className="controls"> <input id="search_field4" type="search" placeholder="Search" onKeyUp={handleKeyUp} /> </div> <output className="console" id="output"> {resultCount} results </output> </div> <HotTable ref={hot4Ref} data={data} colHeaders={true} // enable the `Search` plugin search={{ // add your custom callback function callback: searchResultCounter, }} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> </> );};
export default ExampleComponent;Related API reference
Configuration options
Plugins