React Data GridLoading
Display loading indicators and progress feedback in your data grid application using the Loading plugin.
Overview
The Loading plugin provides a loading overlay for Handsontable using the Dialog plugin. It displays a loading indicator with a customizable title, icon, and description. This is useful for showing progress feedback during data operations, API calls, or any other time-consuming tasks.
With simplicity and effectiveness in mind, the loading plugin was designed to provide a consistent user experience with customizable appearance and behavior. It requires the Dialog plugin to be enabled to function properly.
Basic configuration
To enable the Loading plugin, set the loading
option to true
or provide a configuration object.
import React, { useRef, useEffect } from 'react';
import { HotTable, HotColumn } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
import 'handsontable/styles/handsontable.css';
import 'handsontable/styles/ht-theme-main.css';
// register Handsontable's modules
registerAllModules();
const data = [
{ model: 'Trail Helmet', price: 1298.14, sellDate: 'Aug 31, 2025', sellTime: '02:12 PM', inStock: true },
{ model: 'Windbreaker Jacket', price: 178.9, sellDate: 'May 10, 2025', sellTime: '10:26 PM', inStock: false },
{ model: 'Cycling Cap', price: 288.1, sellDate: 'Sep 15, 2025', sellTime: '09:37 AM', inStock: true },
{ model: 'HL Mountain Frame', price: 94.49, sellDate: 'Jan 17, 2025', sellTime: '02:19 PM', inStock: false },
{ model: 'Racing Socks', price: 430.38, sellDate: 'May 10, 2025', sellTime: '01:42 PM', inStock: true },
{ model: 'Racing Socks', price: 138.85, sellDate: 'Sep 20, 2025', sellTime: '02:48 PM', inStock: true },
{ model: 'HL Mountain Frame', price: 1909.63, sellDate: 'Sep 5, 2025', sellTime: '09:35 AM', inStock: false },
{ model: 'Carbon Handlebar', price: 1080.7, sellDate: 'Oct 24, 2025', sellTime: '10:58 PM', inStock: false },
{ model: 'Aero Bottle', price: 1571.13, sellDate: 'May 24, 2025', sellTime: '12:24 AM', inStock: true },
{ model: 'Windbreaker Jacket', price: 919.09, sellDate: 'Jul 16, 2025', sellTime: '07:11 PM', inStock: true },
{ model: 'HL Road Tire', price: 886.22, sellDate: 'Sep 9, 2025', sellTime: '12:42 AM', inStock: false },
{ model: 'Speed Gloves', price: 635.13, sellDate: 'Nov 17, 2025', sellTime: '12:45 PM', inStock: true },
{ model: 'Trail Helmet', price: 1440.64, sellDate: 'Jan 3, 2025', sellTime: '08:16 PM', inStock: false },
{ model: 'Aero Bottle', price: 944.63, sellDate: 'Nov 15, 2025', sellTime: '04:14 PM', inStock: false },
{ model: 'Windbreaker Jacket', price: 1161.43, sellDate: 'Jun 24, 2025', sellTime: '01:19 PM', inStock: false },
{ model: 'LED Bike Light', price: 1012.5, sellDate: 'May 1, 2025', sellTime: '05:30 PM', inStock: false },
{ model: 'Windbreaker Jacket', price: 635.37, sellDate: 'May 14, 2025', sellTime: '09:05 AM', inStock: true },
{ model: 'Road Tire Tube', price: 1421.27, sellDate: 'Jan 31, 2025', sellTime: '01:33 PM', inStock: true },
{ model: 'Action Camera', price: 1019.05, sellDate: 'Dec 7, 2025', sellTime: '01:26 AM', inStock: false },
{ model: 'Carbon Handlebar', price: 603.96, sellDate: 'Sep 13, 2025', sellTime: '04:10 AM', inStock: false },
];
const ExampleComponent = () => {
const hotTableRef = useRef(null);
useEffect(() => {
const hotInstance = hotTableRef.current?.hotInstance;
if (!hotInstance) {
return;
}
hotInstance.getPlugin('loading').show();
}, []);
return (
<HotTable
ref={hotTableRef}
themeName="ht-theme-main"
data={data}
width="100%"
height={300}
stretchH="all"
contextMenu={true}
rowHeaders={true}
colHeaders={true}
autoWrapRow={true}
autoWrapCol={true}
autoRowSize={true}
loading={true}
licenseKey="non-commercial-and-evaluation"
>
<HotColumn title="Model" type="text" data="model" width={150} headerClassName="htLeft" />
<HotColumn
title="Price"
type="numeric"
data="price"
width={80}
numericFormat={{ pattern: '$0,0.00', culture: 'en-US' }}
className="htRight"
headerClassName="htRight"
/>
<HotColumn
title="Date"
type="date"
data="sellDate"
width={130}
dateFormat="MMM D, YYYY"
correctFormat={true}
className="htRight"
headerClassName="htRight"
/>
<HotColumn
title="Time"
type="time"
data="sellTime"
width={90}
timeFormat="hh:mm A"
correctFormat={true}
className="htRight"
headerClassName="htRight"
/>
<HotColumn title="In stock" type="checkbox" data="inStock" className="htCenter" headerClassName="htCenter" />
</HotTable>
);
};
export default ExampleComponent;
Custom configuration
The loading dialog supports customization of the icon, title, and description.
import React, { useRef, useEffect } from 'react';
import { HotTable, HotColumn } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
import 'handsontable/styles/handsontable.css';
import 'handsontable/styles/ht-theme-main.css';
// register Handsontable's modules
registerAllModules();
const data = [
{ model: 'Trail Helmet', price: 1298.14, sellDate: 'Aug 31, 2025', sellTime: '02:12 PM', inStock: true },
{ model: 'Windbreaker Jacket', price: 178.9, sellDate: 'May 10, 2025', sellTime: '10:26 PM', inStock: false },
{ model: 'Cycling Cap', price: 288.1, sellDate: 'Sep 15, 2025', sellTime: '09:37 AM', inStock: true },
{ model: 'HL Mountain Frame', price: 94.49, sellDate: 'Jan 17, 2025', sellTime: '02:19 PM', inStock: false },
{ model: 'Racing Socks', price: 430.38, sellDate: 'May 10, 2025', sellTime: '01:42 PM', inStock: true },
{ model: 'Racing Socks', price: 138.85, sellDate: 'Sep 20, 2025', sellTime: '02:48 PM', inStock: true },
{ model: 'HL Mountain Frame', price: 1909.63, sellDate: 'Sep 5, 2025', sellTime: '09:35 AM', inStock: false },
{ model: 'Carbon Handlebar', price: 1080.7, sellDate: 'Oct 24, 2025', sellTime: '10:58 PM', inStock: false },
{ model: 'Aero Bottle', price: 1571.13, sellDate: 'May 24, 2025', sellTime: '12:24 AM', inStock: true },
{ model: 'Windbreaker Jacket', price: 919.09, sellDate: 'Jul 16, 2025', sellTime: '07:11 PM', inStock: true },
{ model: 'HL Road Tire', price: 886.22, sellDate: 'Sep 9, 2025', sellTime: '12:42 AM', inStock: false },
{ model: 'Speed Gloves', price: 635.13, sellDate: 'Nov 17, 2025', sellTime: '12:45 PM', inStock: true },
{ model: 'Trail Helmet', price: 1440.64, sellDate: 'Jan 3, 2025', sellTime: '08:16 PM', inStock: false },
{ model: 'Aero Bottle', price: 944.63, sellDate: 'Nov 15, 2025', sellTime: '04:14 PM', inStock: false },
{ model: 'Windbreaker Jacket', price: 1161.43, sellDate: 'Jun 24, 2025', sellTime: '01:19 PM', inStock: false },
{ model: 'LED Bike Light', price: 1012.5, sellDate: 'May 1, 2025', sellTime: '05:30 PM', inStock: false },
{ model: 'Windbreaker Jacket', price: 635.37, sellDate: 'May 14, 2025', sellTime: '09:05 AM', inStock: true },
{ model: 'Road Tire Tube', price: 1421.27, sellDate: 'Jan 31, 2025', sellTime: '01:33 PM', inStock: true },
{ model: 'Action Camera', price: 1019.05, sellDate: 'Dec 7, 2025', sellTime: '01:26 AM', inStock: false },
{ model: 'Carbon Handlebar', price: 603.96, sellDate: 'Sep 13, 2025', sellTime: '04:10 AM', inStock: false },
];
const ExampleComponent = () => {
const hotTableRef = useRef(null);
useEffect(() => {
const hotInstance = hotTableRef.current?.hotInstance;
if (!hotInstance) {
return;
}
hotInstance.getPlugin('loading').show();
}, []);
return (
<HotTable
ref={hotTableRef}
themeName="ht-theme-main"
data={data}
width="100%"
height={300}
stretchH="all"
contextMenu={true}
rowHeaders={true}
colHeaders={true}
autoWrapRow={true}
autoWrapCol={true}
autoRowSize={true}
loading={{
icon: '<svg class="ht-loading__icon-svg" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path stroke="currentColor" stroke-width="2" d="M15 8a7 7 0 1 1-3.5-6.062"></path></svg>',
title: 'Processing Data...',
description: 'Please wait while we load your inventory data...',
}}
licenseKey="non-commercial-and-evaluation"
>
<HotColumn title="Model" type="text" data="model" width={150} headerClassName="htLeft" />
<HotColumn
title="Price"
type="numeric"
data="price"
width={80}
numericFormat={{ pattern: '$0,0.00', culture: 'en-US' }}
className="htRight"
headerClassName="htRight"
/>
<HotColumn
title="Date"
type="date"
data="sellDate"
width={130}
dateFormat="MMM D, YYYY"
correctFormat={true}
className="htRight"
headerClassName="htRight"
/>
<HotColumn
title="Time"
type="time"
data="sellTime"
width={90}
timeFormat="hh:mm A"
correctFormat={true}
className="htRight"
headerClassName="htRight"
/>
<HotColumn title="In stock" type="checkbox" data="inStock" className="htCenter" headerClassName="htCenter" />
</HotTable>
);
};
export default ExampleComponent;
Real-world usage
Here are some common scenarios where the loading dialog is useful:
import React, { useRef, useState } from 'react';
import { HotTable, HotColumn } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
import 'handsontable/styles/handsontable.css';
import 'handsontable/styles/ht-theme-main.css';
// register Handsontable's modules
registerAllModules();
const Table = React.memo(({ hotTableRef, data }) => {
return (
<>
<HotTable
ref={hotTableRef}
themeName="ht-theme-main"
data={data}
width="100%"
height={300}
stretchH="all"
contextMenu={true}
rowHeaders={true}
colHeaders={true}
autoWrapRow={true}
autoWrapCol={true}
autoRowSize={true}
loading={true}
licenseKey="non-commercial-and-evaluation"
>
<HotColumn title="Model" type="text" data="model" width={150} headerClassName="htLeft" />
<HotColumn
title="Price"
type="numeric"
data="price"
width={80}
numericFormat={{ pattern: '$0,0.00', culture: 'en-US' }}
className="htRight"
headerClassName="htRight"
/>
<HotColumn
title="Date"
type="date"
data="sellDate"
width={130}
dateFormat="MMM D, YYYY"
correctFormat={true}
className="htRight"
headerClassName="htRight"
/>
<HotColumn
title="Time"
type="time"
data="sellTime"
width={90}
timeFormat="hh:mm A"
correctFormat={true}
className="htRight"
headerClassName="htRight"
/>
<HotColumn title="In stock" type="checkbox" data="inStock" className="htCenter" headerClassName="htCenter" />
</HotTable>
</>
);
});
const ExampleComponent = React.memo(() => {
const hotTableRef = useRef(null);
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
// Simulate data loading
const loadData = async () => {
const hotInstance = hotTableRef.current?.hotInstance;
if (!hotInstance) {
return;
}
const loadingPlugin = hotInstance.getPlugin('loading');
setIsLoading(true);
// Show loading dialog
loadingPlugin.show();
try {
// Simulate API call delay
await new Promise((resolve) => setTimeout(resolve, 3000));
// Simulated data
setData([
{ model: 'Trail Helmet', price: 1298.14, sellDate: 'Aug 31, 2025', sellTime: '02:12 PM', inStock: true },
{ model: 'Windbreaker Jacket', price: 178.9, sellDate: 'May 10, 2025', sellTime: '10:26 PM', inStock: false },
{ model: 'Cycling Cap', price: 288.1, sellDate: 'Sep 15, 2025', sellTime: '09:37 AM', inStock: true },
{ model: 'HL Mountain Frame', price: 94.49, sellDate: 'Jan 17, 2025', sellTime: '02:19 PM', inStock: false },
{ model: 'Racing Socks', price: 430.38, sellDate: 'May 10, 2025', sellTime: '01:42 PM', inStock: true },
{ model: 'Racing Socks', price: 138.85, sellDate: 'Sep 20, 2025', sellTime: '02:48 PM', inStock: true },
{ model: 'HL Mountain Frame', price: 1909.63, sellDate: 'Sep 5, 2025', sellTime: '09:35 AM', inStock: false },
{ model: 'Carbon Handlebar', price: 1080.7, sellDate: 'Oct 24, 2025', sellTime: '10:58 PM', inStock: false },
{ model: 'Aero Bottle', price: 1571.13, sellDate: 'May 24, 2025', sellTime: '12:24 AM', inStock: true },
{ model: 'Windbreaker Jacket', price: 919.09, sellDate: 'Jul 16, 2025', sellTime: '07:11 PM', inStock: true },
]);
// Load data into the table
hotTableRef.current?.hotInstance?.loadData(data);
// Hide loading dialog
loadingPlugin.hide();
setIsLoading(false);
} catch (error) {
// Handle error
setTimeout(() => {
loadingPlugin.hide();
setIsLoading(false);
}, 2000);
}
};
return (
<>
<div style={{ marginBottom: '16px', display: 'flex', gaap: '10px' }}>
<button id="loadData" onClick={loadData} disabled={isLoading}>
{data.length > 0 ? 'Reload Data' : 'Load Data'}
</button>
</div>
<Table hotTableRef={hotTableRef} data={data} />
</>
);
});
export default ExampleComponent;
Localize loading
Translate default loading dialog labels using the global translations mechanism. The loading dialog introduces the following keys to the language dictionary that you can use to translate the loading UI:
LOADING_TITLE = 'Loading...'
To learn more about the translation mechanism, see the Languages guide.
Related API reference
There is a newer version of Handsontable available. Switch to the latest version ⟶