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.
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.
Feel free to share demo-related thoughts here.
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Thank you for the feedback!
If you have technical questions, please create a support ticket in the DevExpress Support Center.
Backend API
import React 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,
});
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
updateFrequency: 100,
};
this.onUpdateFrequencyChanged = this.onUpdateFrequencyChanged.bind(this);
this.detailRender = this.detailRender.bind(this);
setInterval(() => {
if (getOrderCount() > 500000) {
return;
}
for (let i = 0; i < this.state.updateFrequency / 20; i += 1) {
addOrder();
}
}, 50);
}
render() {
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={this.detailRender}>
</MasterDetail>
</DataGrid>
<div className="options">
<div className="caption">Options</div>
<div className="option">
<span>Update frequency (per second):</span>
<Slider
min={10}
step={10}
max={5000}
value={this.state.updateFrequency}
onValueChanged={this.onUpdateFrequencyChanged}>
<Tooltip
enabled={true}
showMode="always"
position="top">
</Tooltip>
</Slider>
</div>
</div>
</div>
);
}
detailRender(detail) {
return (
<DataGrid
dataSource={this.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={this.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>
);
}
onUpdateFrequencyChanged(e) {
this.setState({
updateFrequency: e.value,
});
}
getDetailGridDataSource(product) {
return {
store: ordersStore,
reshapeOnPush: true,
filter: ['ProductID', '=', product.ProductID],
};
}
getAmount(order) {
return order.UnitPrice * order.Quantity;
}
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
ReactDOM.render(
<App />,
document.getElementById('app'),
);
<!DOCTYPE html>
<html>
<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=1.0" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/23.1.5/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.js");
</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;
}
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,
},
}]);
}
window.exports = window.exports || {};
window.config = {
transpiler: 'plugin-babel',
meta: {
'devextreme/localization.js': {
'esModule': true,
},
},
paths: {
'npm:': 'https://unpkg.com/',
},
defaultExtension: 'js',
map: {
'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@23.1.6/cjs',
'devextreme-react': 'npm:devextreme-react@23.1.6',
'jszip': 'npm:jszip@3.7.1/dist/jszip.min.js',
'devextreme-quill': 'npm:devextreme-quill@1.6.2/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.2/dist/dx-diagram.js',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.49/dist/dx-gantt.js',
'@devextreme/runtime': 'npm:@devextreme/runtime@3.0.12',
'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',
// 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.4/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.4/parser-html.js',
},
packages: {
'devextreme': {
defaultExtension: 'js',
},
'devextreme-react': {
main: 'index.js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
'npm:@devextreme/runtime@3.0.12/inferno/package.json',
],
babelOptions: {
sourceMaps: false,
stage0: true,
react: true,
},
};
System.config(window.config);