2023-04-07 08:30:20 +00:00
---
2023-08-17 20:30:13 +00:00
title: Let Data Glide on Glide Data Grid
sidebar_label: Glide Data Grid
description: Display structured data with Glide Data Grid. Effortlessly import and export data using SheetJS. Modernize business processes while retaining legacy Excel structures.
2023-04-07 08:30:20 +00:00
pagination_prev: demos/frontend/index
pagination_next: demos/net/index
---
2023-04-27 09:12:19 +00:00
import current from '/version.js';
2023-05-01 01:27:02 +00:00
import CodeBlock from '@theme/CodeBlock';
2023-04-27 09:12:19 +00:00
2023-08-17 20:30:13 +00:00
[Glide Data Grid ](https://grid.glideapps.com/ ) is a high-performance data grid
designed for the ReactJS web framework.
[SheetJS ](https://sheetjs.com ) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses Glide Data Grid and SheetJS to pull data from a spreadsheet and
display the content in a data grid. We'll explore how to import data from files
into the data grid and how to export modified data from the grid to workbooks.
The ["Demo" ](#demo ) section includes a complete example that displays data from
user-supplied sheets and exports data to XLSX workbooks:
![Glide Data Grid example ](pathname:///gdg/gdg.png )
2023-04-07 08:30:20 +00:00
:::note
2023-08-17 20:30:13 +00:00
This demo was last tested on 2023 August 17 with Glide Data Grid 5.2.1
2023-04-07 08:30:20 +00:00
:::
## Integration Details
2023-08-17 20:30:13 +00:00
[The "Frameworks" section ](/docs/getting-started/installation/frameworks ) covers
installation with Yarn and other package managers.
Using the `npm` tool, this command installs SheetJS and Glide Data Grid:
< CodeBlock language = "bash" > {`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @glideapps/glide -data-grid@5.2.1`}
< / CodeBlock >
Methods and components in both libraries can be loaded in pages using `import` :
```js
import { read, utils, writeFile } from 'xlsx';
import { DataEditor, GridCellKind, GridCell, Item } from '@glideapps/glide-data-grid';
```
:::caution pass
Glide Data Grid is primarily event-based. It does not manage state directly.
Instead, developers are expected to manage updates when users edit cells.
:::
2023-04-07 08:30:20 +00:00
#### Backing Store
Under the hood, the `DataEditor` component is designed to call methods and
request data to display in the grid. It is typical to store data *outside* of
component state. A `getCellContent` callback will pull data from the external
2023-08-17 20:30:13 +00:00
backing store, while SheetJS operations will directly act on the store.
For this demo, there are two parts to the data store:
- `data` is an "Array of Objects" that will hold the raw data[^1].
- `header` is an array of the header names
:::info pass
Following the Glide Data Grid conventions[^2], both objects are defined at the top
level of the component script. They are declared outside of the component!
:::
2023-04-07 08:30:20 +00:00
```js
2023-08-17 20:30:13 +00:00
// !! THESE ARRAYS ARE DEFINED OUTSIDE OF THE COMPONENT FUNCTION !!
2023-04-07 08:30:20 +00:00
// this will store the raw data objects
let data: any[] = [];
// this will store the header names
let header: string[] = [];
```
#### Props
2023-08-17 20:30:13 +00:00
:::note pass
2023-04-07 08:30:20 +00:00
2023-08-17 20:30:13 +00:00
This is a high-level overview. The official documentation should be consulted.[^3]
2023-04-07 08:30:20 +00:00
:::
_Columns_
`DataEditor` expects column metadata to be passed through a `columns` prop. This
should be managed in the component state:
```js
import { useState } from 'react';
import { DataEditor, GridColumn } from '@glideapps/glide-data-grid';
function App() {
// highlight-next-line
const [cols, setCols] = useState< GridColumn [ ] > ([]); // gdg column objects
// ...
return ( < >
// ...
< DataEditor
// ... props
// highlight-next-line
columns={cols}
/>
// ...
< /> );
}
export default App;
```
Each `GridColumn` object expects a `title` representing the display name and an
`id` representing the key to index within the data object.
_Data_
The `DataEditor` component expects a `getCellContent` callback for supplying
data. The callback accepts column and row indices. The column index should be
used to find the header key:
```js
import { useCallback } from 'react';
import { DataEditor, GridCellKind, GridCell, Item } from '@glideapps/glide-data-grid';
// ...
function App() {
// ...
// backing data store -> gdg
// highlight-start
const getContent = useCallback((cell: Item): GridCell => {
const [col, row] = cell;
return {
kind: GridCellKind.Text,
// header[col] is the name of the field
displayData: String(data[row]?.[header[col]]??""),
data: data[row]?.[header[col]],
};
}, []);
// highlight-end
// ...
return ( < >
// ...
< DataEditor
// ... props
// highlight-next-line
getCellContent={getContent}
/>
// ...
< /> );
}
```
_Row Count_
`DataEditor` also accepts a `rows` property indicating the number of rows. This
is best managed in state:
```js
import { useState } from 'react';
import { DataEditor } from '@glideapps/glide-data-grid';
function App() {
// highlight-next-line
const [rows, setRows] = useState< number > (0); // number of rows
// ...
return ( < >
// ...
< DataEditor
// ... props
// highlight-next-line
rows={rows}
/>
// ...
< /> );
}
export default App;
```
_Editing Data_
The demo uses the `onCellEdited` callback to write back to the data store.
### Parsing Data
_SheetJS to Data Store_
2023-08-17 20:30:13 +00:00
The SheetJS `read` method parses data from a number of sources[^4]. It returns a
workbook object which holds worksheet objects and other data[^5].
Raw data objects can be generated with the SheetJS `sheet_to_json` function[^6].
The headers can be pulled from the first row of the sheet. The `sheet_to_json`
method accepts a `range` option, and other SheetJS API functions can be used to
calculate the correct range for the header names[^7].
This example generates row objects from the first sheet in the workbook:
2023-04-07 08:30:20 +00:00
```js
import { utils, WorkBook } from 'xlsx';
// ...
const update_backing_store = (wb: WorkBook) => {
// get first worksheet
const sheet = wb.Sheets[wb.SheetNames[0]];
// set data
// highlight-next-line
data = utils.sheet_to_json< any > (sheet);
// create a range consisting of the first row
const range = utils.decode_range(sheet["!ref"]??"A1"); // original range
range.e.r = range.s.r; // set ending row to starting row (select first row)
// pull headers
// highlight-next-line
header = utils.sheet_to_json< string [ ] > (sheet, {header: 1, range})[0];
};
// ...
```
2023-04-19 20:03:23 +00:00
_Importing from Data Store_
2023-04-07 08:30:20 +00:00
Scheduling a refresh for the `DataEditor` involves updating the grid column
metadata and row count through the standard state. It also requires a special
`updateCells` call to instruct the grid to mark the cached data as stale:
```js
import { useRef } from 'react'
import { WorkBook } from 'xlsx'
import { DataEditor, GridColumn, Item, DataEditorRef } from '@glideapps/glide-data-grid'
function App() {
const ref = useRef< DataEditorRef > (null); // gdg ref
// ...
const parse_wb = (wb: WorkBook) => {
update_backing_store(wb);
// highlight-start
// update column metadata by pulling from external header keys
setCols(header.map(h => ({title: h, id: h} as GridColumn)));
// update number of rows
setRows(data.length);
if(data.length > 0) {
// create an array of the cells that must be updated
let cells = data.map(
(_,R) => Array.from({length:header.length}, (_,C) => ({cell: ([C,R] as Item)}))
).flat();
// initiate update using the `ref` attached to the DataEditor
ref.current?.updateCells(cells)
}
// highlight-end
};
// ...
return ( < >
// ...
< DataEditor
// ... props
// highlight-next-line
ref={ref}
/>
// ...
< /> );
}
export default App;
```
### Writing Data
2023-08-17 20:30:13 +00:00
The SheetJS `json_to_sheet` method generates worksheet objects directly from
the `data` array[^8]:
2023-04-07 08:30:20 +00:00
```js
const ws = utils.json_to_sheet(data); // easy :)
```
2023-08-17 20:30:13 +00:00
The worksheet can be exported to XLSX by creating a SheetJS workbook object[^9]
and writing with `writeFile` or `writeFileXLSX` [^10]:
```js
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "Sheet1");
writeFileXLSX(wb, "sheetjs-gdg.xlsx");
```
2023-04-07 08:30:20 +00:00
Since the editor can change the header titles, it is strongly recommended to
pull column data from the state and rewrite the header row:
```js
import { utils, writeFileXLSX } from 'xlsx';
function App() {
// ...
const exportXLSX = useCallback(() => {
// highlight-start
// generate worksheet using data with the order specified in the columns array
const ws = utils.json_to_sheet(data, {header: cols.map(c => c.id ?? c.title)});
// rewrite header row with titles
utils.sheet_add_aoa(ws, [cols.map(c => c.title ?? c.id)], {origin: "A1"});
// highlight-end
// create workbook
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "Export"); // replace with sheet name
// download file
writeFileXLSX(wb, "sheetjs-gdg.xlsx");
}, []);
// ...
return ( < >
// ...
// highlight-next-line
< button onClick = {exportXLSX} > < b > Export XLSX!< / b > < / button >
// ...
< /> );
}
export default App;
```
## Demo
2023-08-17 20:30:13 +00:00
1) Create a new project from the `react-ts` template:
2023-04-07 08:30:20 +00:00
```bash
npm create vite@latest -- sheetjs-gdg --template react-ts
cd sheetjs-gdg
npm i
```
Install SheetJS and Glide Data Grid required dependencies:
2023-05-01 01:27:02 +00:00
< CodeBlock language = "bash" > {`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz @glideapps/glide -data-grid@5.2.1`}
< / CodeBlock >
2023-04-07 08:30:20 +00:00
Start dev server:
```bash
npm run dev
```
The terminal window will display a URL (typically `http://localhost:5173` ).
Open the URL with a web browser.
2) Download [`App.tsx` ](pathname:///gdg/App.tsx ) and replace `src/App.tsx` :
```bash
curl -L -o src/App.tsx https://docs.sheetjs.com/gdg/App.tsx
```
Refresh the browser window and a grid should be displayed:
![glide-data-grid initial view ](pathname:///gdg/pre.png )
3) To test the export functionality, make some changes to the grid data.
2023-08-17 20:30:13 +00:00
Some statisticians believe President Grover Cleveland should be counted once.
2023-04-07 08:30:20 +00:00
That would imply President Clinton should be index 41 and the indices of the
other presidents should be decremented. By double-clicking on each cell in the
Index column, a cell editor should appear. Decrement each index:
![glide-data-grid after edits ](pathname:///gdg/post.png )
Click on the "Export" button to create a file! Open the file and verify.
2023-08-17 20:30:13 +00:00
[^1]: See ["Array of Objects" in the ReactJS demo ](/docs/demos/frontend/react#array-of-objects )
[^2]: The "Story" section of the ["Getting Started" page in the Glide Data Grid Storybook ](https://quicktype.github.io/glide-data-grid/?path=/story/glide-data-grid-docs--getting-started ) stores an Array of Objects outside of the component.
[^3]: See [the "Data" section in `DataEditorProps` ](https://grid.glideapps.com/docs/interfaces/DataEditorProps.html#columns:~:text=default-,Data,-Readonly ) in the Glide Data Grid API documentation.
[^4]: See [`read` in "Reading Files" ](/docs/api/parse-options )
[^5]: See ["SheetJS Data Model" ](/docs/csf )
[^6]: See [`sheet_to_json` in "Utilities" ](/docs/api/utilities/array#array-output )
[^7]: ["Addresses and Ranges" ](/docs/csf/general ) covers general concepts and utility functions including [`decode_range` ](/docs/csf/general#cell-ranges-1 ).
[^8]: See ["Array of Objects Input" in "Utilities" ](/docs/api/utilities/array#array-of-objects-input )
[^9]: See ["Workbook Helpers" in "Utilities" ](/docs/api/utilities/wb ) for details on `book_new` and `book_append_sheet` .
[^10]: See [`writeFile` in "Writing Files" ](/docs/api/write-options )