Get All Data From Devextreme Datagrid in Virtual Scrolling
Sometimes, we need to display a large amount of data on the frontend. When we do this with table elements, we may encounter performance issues depending on the size of the data. To overcome performance issues and meet additional requirements such as filtering and fetching data in chunks, we sometimes turn to third-party libraries. DevExpress’s Devextreme library is one of the most commonly used libraries for these situations.
In this article, I will address one of the potential issues that may arise when using Devextreme with React. As we know, React manages data control with a state. However, the Devextreme library goes beyond this and has its own data management structure, including datasources that you can use for fetching data in chunks and adding filters to requests.
If you are using a Devextreme Datagrid and need to fetch data from it, you might encounter issues depending on the method you use. Let’s create a scenario related to this and examine the possible solutions.
Install Devextreme Datagrid In React
Let’s quickly set up a React application and integrate the Devextreme Datagrid.
npm create vite@latest
Afterwards, let’s clean up the main.jsx file and include Devextreme’s CSS file.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
// DEVEXTREME
import 'devextreme/dist/css/dx.light.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
Let’s perform a similar process for App.jsx as well.
import { Button } from "devextreme-react";
import "./App.css";
function App() {
return (
<>
<Button text="Click me" onClick={(val) => console.log(val)} />
</>
);
}
export default App;
Let’s simplify the App.css file as follows:
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
}
When we compile the project, a button will appear as shown below, and we will have completed the installation of the Devextreme library.
Creating a Scenario and Default Data
Let’s write a function to generate mock data for examining potential issues when retrieving data from a Datagrid component that is created without being dependent on state (an uncontrolled structure).
// PRODUCT SAMPLE DATA
const productList = useMemo(() => {
return [...Array(1000).keys()].map((itm) => ({
id: itm + 1,
title: `Product ${itm + 1}`,
price: Math.floor(Math.random() * 100),
}));
}, []);
In the above function, we created mock product data with fields for ID, title, and price. To prevent potential performance issues that might arise from re-rendering due to changes, we used the useMemo
hook.
Inclusion of the Devextreme Datagrid Component
In a previous step, we called the Button component to test whether the library was loaded correctly. In the current step, we will call a data table that will display 1000 rows of data.
We will use Devextreme’s Datagrid component for the data table. To enhance performance, we will include virtual scrolling, and for data operations, we will add the editing feature. If we break it down step by step:
Styles for Datagrid Virtual Scrolling
When using Datagrid with Infinite Scrolling or Virtual Scrolling, the Datagrid component should be placed within a fixed-height div element, and this div element should not overflow its content.
To meet these conditions, you can add the grid-wrapper
class to your App.css file and use it.
.grid-wrapper{
height:calc(100vh - 200px);
position: relative;
overflow: hidden;
}
Including the Datagrid Component
With the necessary preparations in place, we can now include the Datagrid component and the Scrolling and Editing features we plan to use within the component. In this section, we also need the Toolbar component because we will be adding a “Save” button to the toolbar.
// DEVEXTREME
import { Button } from "devextreme-react/button";
import DataGrid, {
Editing,
Item,
Scrolling,
Toolbar,
} from "devextreme-react/data-grid";
With the required libraries included, you can now call the Devextreme components in the App.js file, along with the containing div.
<div className="grid-wrapper">
{/* DATAGRID */}
<DataGrid
dataSource={productList}
rowAlternationEnabled={true}
showBorders={true}
width="100%"
height="100%"
>
{/* TOOLBAR */}
<Toolbar>
{/* SAVE BUTTON */}
<Item location="before">
<Button icon="save" type="default" stylingMode="contained" />
</Item>
{/* NEW ROW BUTTON */}
<Item name="addRowButton" />
</Toolbar>
{/* EDITING */}
<Editing
allowUpdating={true}
allowAdding={true}
allowDeleting={true}
mode="cell"
/>
{/* VIRTUAL SCROLLING */}
<Scrolling mode="virtual" />
</DataGrid>
</div>
After this step, your screen will look like the following:
On this screen, we can now perform viewing, adding, editing, and deleting operations on the product data we’ve created. But what if we want to retrieve the final state of the data?
In our example, since the Datagrid component we created is in an uncontrolled structure, we need to assign a reference to the component and retrieve the data using that reference.
// GRID REF
const gridRef = useRef(null);
We can create a reference object with the useRef hook, as shown above. We can then add the reference we’ve created to the Datagrid component as follows.
<DataGrid
ref={gridRef}
...
Printing Datagrid Data to the Console
With our data and Datagrid component ready, we can now proceed with the process of printing to the console.
const printGridData = () => {
const gridInstance = gridRef.current?.instance;
const gridItems = gridInstance?.getDataSource()?.items();
if (Array.isArray(gridItems)) {
console.log("Array.length: ", gridItems.length);
console.table(gridItems);
}
};
With the gridRef reference we provided to the Datagrid above, we can access the component’s datasource and retrieve its data. Now, let’s link the printGridData
function we wrote to the save button.
{/* SAVE BUTTON */}
<Item location="before">
<Button
icon="save"
type="default"
stylingMode="contained"
onClick={printGridData}
/>
</Item>
With the application in its current state, when we click the save button, we’ll get a result like the following:
As we can see in the example above, even though we have 1000 data entries, only the first 60 data entries were printed. As mentioned earlier, when using the Infinite and Virtual scrolling models, this is the behavior you will encounter. When using the standard mode, you would need to set the paging value to be greater than or equal to the number of data entries to display all the items. However, this can also lead to performance issues. Let’s now explore how to retrieve all the data.
const printGridData = async () => {
const gridInstance = gridRef.current?.instance;
// const gridItems = gridInstance?.getDataSource()?.items();
const gridStore = gridInstance?.getDataSource()?.store();
const loadedGridItems = await gridStore?.load();
if (Array.isArray(loadedGridItems)) {
console.log("Array.length: ", loadedGridItems.length);
console.table(loadedGridItems);
}
};
If we modify the printGridData
function to be asynchronous as shown above and can access the Datagrid’s store, we can then use the load
function to retrieve all the data.
Using this method, you also address the issue of retrieving data that hasn’t been saved yet. To clarify further, when making edits on the Datagrid and then pressing the save button before exiting the cell, the saved button will display the old data from the cell you were editing, not the updated data.
Therefore, if you are using the gridInstance?.getDataSource()?.items();
method, you should check whether there is any unsaved data beforehand and, if there is, save the data before retrieving it.
const gridHasEditData = gridInstance?.hasEditData();
if (gridHasEditData) {
await gridInstance?.saveEditData();
}
When we access the Datagrid’s store and perform a load operation, this requirement is no longer necessary.
Conclusion
To retrieve data from the Devextreme Datagrid component in an uncontrolled manner, you can provide a reference to the Datagrid, and by loading its store, you can fetch the data.
You can access the source code of the example in the article by clicking here on GitHub.
Leave a Reply