DevExtreme v24.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

React Data Grid - Real-Time Updates

The DataGrid component is optimized for real-time updates. You should bind it to a DataSource that contains a store and use the push(changes) method to update the store's data. The rows with changed data are rerendered (see the DataGrid's repaintChangesOnly property). The DataSource object also provides properties that control its behavior when it receives a push. For example, reshapeOnPush is set to true in this demo to allow summaries to be recalculated.

Backend API
import React, { useEffect, useState } from 'react'; import DataGrid, { Column, Summary, TotalItem, MasterDetail, Paging, DataGridTypes, } from 'devextreme-react/data-grid'; import { Slider, SliderTypes, Tooltip } from 'devextreme-react/slider'; import DataSource from 'devextreme/data/data_source'; import { productsStore, ordersStore, getOrderCount, addOrder, Product, } from './data.ts'; const dataSource = new DataSource({ store: productsStore, reshapeOnPush: true, }); const getDetailGridDataSource = (product: Product) => ({ store: ordersStore, reshapeOnPush: true, filter: ['ProductID', '=', product.ProductID], }); const getAmount = (order) => order.UnitPrice * order.Quantity; const detailRender = (detail: DataGridTypes.MasterDetailTemplateData) => ( <DataGrid dataSource={getDetailGridDataSource(detail.data)} repaintChangesOnly={true} columnAutoWidth={true} showBorders={true}> <Paging defaultPageSize={5} /> <Column dataField="OrderID" dataType="number" /> <Column dataField="ShipCity" dataType="string" /> <Column dataField="OrderDate" dataType="datetime" format="yyyy/MM/dd HH:mm:ss" /> <Column dataField="UnitPrice" dataType="number" format="currency" /> <Column dataField="Quantity" dataType="number" /> <Column caption="Amount" dataType="number" format="currency" allowSorting={true} calculateCellValue={getAmount} /> <Summary> <TotalItem column="OrderID" summaryType="count" /> <TotalItem column="Quantity" summaryType="sum" displayFormat="{0}" /> <TotalItem column="Amount" summaryType="sum" displayFormat="{0}" valueFormat="currency" /> </Summary> </DataGrid> ); const App = () => { const [updateFrequency, setUpdateFrequency] = useState(100); useEffect(() => { const interval = setInterval(() => { if (getOrderCount() > 500000) { return; } for (let i = 0; i < updateFrequency / 20; i += 1) { addOrder(); } }, 50); return () => clearInterval(interval); }, [updateFrequency]); const onUpdateFrequencyChanged = (e: SliderTypes.ValueChangedEvent) => { setUpdateFrequency(e.value); }; return ( <div> <DataGrid dataSource={dataSource} repaintChangesOnly={true} columnAutoWidth={true} showBorders={true}> <Paging defaultPageSize={10} /> <Column dataField="ProductName" dataType="string" /> <Column dataField="UnitPrice" dataType="number" format="currency" /> <Column dataField="OrderCount" dataType="number" /> <Column dataField="Quantity" dataType="number" /> <Column dataField="Amount" dataType="number" format="currency" /> <Summary> <TotalItem column="ProductName" summaryType="count" /> <TotalItem column="Amount" summaryType="sum" displayFormat="{0}" valueFormat="currency" /> <TotalItem column="OrderCount" summaryType="sum" displayFormat="{0}" /> </Summary> <MasterDetail enabled={true} render={detailRender}> </MasterDetail> </DataGrid> <div className="options"> <div className="caption">Options</div> <div className="option"> <span>Update frequency:</span> <Slider min={10} step={10} max={5000} value={updateFrequency} onValueChanged={onUpdateFrequencyChanged}> <Tooltip enabled={true} format="#0 per second" showMode="always" position="top"> </Tooltip> </Slider> </div> </div> </div> ); }; export default App;
import React, { useEffect, useState } from 'react'; import DataGrid, { Column, Summary, TotalItem, MasterDetail, Paging, } from 'devextreme-react/data-grid'; import { Slider, Tooltip } from 'devextreme-react/slider'; import DataSource from 'devextreme/data/data_source'; import { productsStore, ordersStore, getOrderCount, addOrder, } from './data.js'; const dataSource = new DataSource({ store: productsStore, reshapeOnPush: true, }); const getDetailGridDataSource = (product) => ({ store: ordersStore, reshapeOnPush: true, filter: ['ProductID', '=', product.ProductID], }); const getAmount = (order) => order.UnitPrice * order.Quantity; const detailRender = (detail) => ( <DataGrid dataSource={getDetailGridDataSource(detail.data)} repaintChangesOnly={true} columnAutoWidth={true} showBorders={true} > <Paging defaultPageSize={5} /> <Column dataField="OrderID" dataType="number" /> <Column dataField="ShipCity" dataType="string" /> <Column dataField="OrderDate" dataType="datetime" format="yyyy/MM/dd HH:mm:ss" /> <Column dataField="UnitPrice" dataType="number" format="currency" /> <Column dataField="Quantity" dataType="number" /> <Column caption="Amount" dataType="number" format="currency" allowSorting={true} calculateCellValue={getAmount} /> <Summary> <TotalItem column="OrderID" summaryType="count" /> <TotalItem column="Quantity" summaryType="sum" displayFormat="{0}" /> <TotalItem column="Amount" summaryType="sum" displayFormat="{0}" valueFormat="currency" /> </Summary> </DataGrid> ); const App = () => { const [updateFrequency, setUpdateFrequency] = useState(100); useEffect(() => { const interval = setInterval(() => { if (getOrderCount() > 500000) { return; } for (let i = 0; i < updateFrequency / 20; i += 1) { addOrder(); } }, 50); return () => clearInterval(interval); }, [updateFrequency]); const onUpdateFrequencyChanged = (e) => { setUpdateFrequency(e.value); }; return ( <div> <DataGrid dataSource={dataSource} repaintChangesOnly={true} columnAutoWidth={true} showBorders={true} > <Paging defaultPageSize={10} /> <Column dataField="ProductName" dataType="string" /> <Column dataField="UnitPrice" dataType="number" format="currency" /> <Column dataField="OrderCount" dataType="number" /> <Column dataField="Quantity" dataType="number" /> <Column dataField="Amount" dataType="number" format="currency" /> <Summary> <TotalItem column="ProductName" summaryType="count" /> <TotalItem column="Amount" summaryType="sum" displayFormat="{0}" valueFormat="currency" /> <TotalItem column="OrderCount" summaryType="sum" displayFormat="{0}" /> </Summary> <MasterDetail enabled={true} render={detailRender} ></MasterDetail> </DataGrid> <div className="options"> <div className="caption">Options</div> <div className="option"> <span>Update frequency:</span> <Slider min={10} step={10} max={5000} value={updateFrequency} onValueChanged={onUpdateFrequencyChanged} > <Tooltip enabled={true} format="#0 per second" showMode="always" position="top" ></Tooltip> </Slider> </div> </div> </div> ); }; export default App;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('app'), );
import ArrayStore from 'devextreme/data/array_store'; const cities = ['Los Angeles', 'Denver', 'Bentonville', 'Atlanta', 'Reno', 'Beaver', 'Malibu', 'Phoenix', 'San Diego', 'Little Rock', 'Pasadena', 'Boise', 'San Jose', 'Chatsworth', 'San Fernando', 'South Pasadena', 'San Fernando Valley', 'La Canada', 'St. Louis']; const orders = []; export interface Product { ProductID: number; ProductName: string; UnitPrice: number; Quantity: number; Amount: number; OrderCount: number; } const products: Product[] = []; for (let i = 1; i <= 100; i += 1) { products.push({ ProductID: i, ProductName: `Product ${i}`, UnitPrice: Math.floor(Math.random() * 1000) + 1, Quantity: 0, Amount: 0, OrderCount: 0, }); } export const productsStore = new ArrayStore({ key: 'ProductID', data: products, }); export const ordersStore = new ArrayStore({ key: 'OrderID', data: orders, }); export function getOrderCount() { return orders.length; } export function addOrder() { const product = products[Math.round(Math.random() * 99)]; const order = { OrderID: orders.length ? orders[orders.length - 1].OrderID + 1 : 20001, ShipCity: cities[Math.round(Math.random() * (cities.length - 1))], ProductID: product.ProductID, UnitPrice: product.UnitPrice, OrderDate: new Date(), Quantity: Math.round(Math.random() * 20) + 1, }; ordersStore.push([{ type: 'insert', data: order }]); productsStore.push([{ type: 'update', key: order.ProductID, data: { OrderCount: product.OrderCount + 1, Quantity: product.Quantity + order.Quantity, Amount: product.Amount + order.UnitPrice * order.Quantity, }, }]); }
window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types@15.8.1/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'devextreme': 'npm:devextreme@24.1.7/cjs', 'devextreme-react': 'npm:devextreme-react@24.1.7/cjs', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.12/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.56/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.13', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // SystemJS plugins 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: 'json', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.13/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('app'));
import ArrayStore from 'devextreme/data/array_store'; const cities = [ 'Los Angeles', 'Denver', 'Bentonville', 'Atlanta', 'Reno', 'Beaver', 'Malibu', 'Phoenix', 'San Diego', 'Little Rock', 'Pasadena', 'Boise', 'San Jose', 'Chatsworth', 'San Fernando', 'South Pasadena', 'San Fernando Valley', 'La Canada', 'St. Louis', ]; const orders = []; const products = []; for (let i = 1; i <= 100; i += 1) { products.push({ ProductID: i, ProductName: `Product ${i}`, UnitPrice: Math.floor(Math.random() * 1000) + 1, Quantity: 0, Amount: 0, OrderCount: 0, }); } export const productsStore = new ArrayStore({ key: 'ProductID', data: products, }); export const ordersStore = new ArrayStore({ key: 'OrderID', data: orders, }); export function getOrderCount() { return orders.length; } export function addOrder() { const product = products[Math.round(Math.random() * 99)]; const order = { OrderID: orders.length ? orders[orders.length - 1].OrderID + 1 : 20001, ShipCity: cities[Math.round(Math.random() * (cities.length - 1))], ProductID: product.ProductID, UnitPrice: product.UnitPrice, OrderDate: new Date(), Quantity: Math.round(Math.random() * 20) + 1, }; ordersStore.push([{ type: 'insert', data: order }]); productsStore.push([ { type: 'update', key: order.ProductID, data: { OrderCount: product.OrderCount + 1, Quantity: product.Quantity + order.Quantity, Amount: product.Amount + order.UnitPrice * order.Quantity, }, }, ]); }
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.1.7/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import("./index.tsx"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
.options { padding: 20px; margin-top: 20px; background-color: rgba(191, 191, 191, 0.15); } .caption { font-size: 18px; font-weight: 500; } .option { margin-top: 10px; display: flex; align-items: center; } .option > span { position: relative; top: 2px; margin-right: 10px; } .option > .dx-widget { width: 500px; display: inline-block; vertical-align: middle; }

Grids in the detail sections are updated in real time as well. All the grids share the same ordersStore that receives pushes, but it is filtered to show only a specific product's orders for each grid.