DevExtreme v24.2 is now available.

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

Your search did not match any results.

React Data Grid - Cascading Lookups

You can assign a lookup editor to a column. This editor displays a drop-down list populated with values from the specified data source. Users can filter the drop-down list to quickly locate required values.

Backend API
import React from 'react'; import DataGrid, { Column, DataGridTypes, Editing, Lookup, } from 'devextreme-react/data-grid'; import { employees, states, cities, Employee, } from './data.ts'; const onEditorPreparing = (e: DataGridTypes.EditorPreparingEvent) => { if (e.parentType === 'dataRow' && e.dataField === 'CityID') { const isStateNotSet = e.row.data.StateID === undefined; e.editorOptions.disabled = isStateNotSet; } }; const getFilteredCities = (options: { data?: Employee; }) => ({ store: cities, filter: options.data ? ['StateID', '=', options.data.StateID] : null, }); function setStateValue(rowData: Employee, value) { rowData.CityID = null; this.defaultSetCellValue(rowData, value); } const App = () => ( <div id="data-grid-demo"> <DataGrid dataSource={employees} keyExpr="ID" showBorders={true} onEditorPreparing={onEditorPreparing} > <Editing mode="row" allowUpdating={true} allowAdding={true}> </Editing> <Column dataField="FirstName" /> <Column dataField="LastName" /> <Column dataField="Position" /> <Column dataField="StateID" caption="State" setCellValue={setStateValue}> <Lookup dataSource={states} displayExpr="Name" valueExpr="ID" /> </Column> <Column dataField="CityID" caption="City"> <Lookup dataSource={getFilteredCities as any} displayExpr="Name" valueExpr="ID" /> </Column> </DataGrid> </div> ); export default App;
import React from 'react'; import DataGrid, { Column, Editing, Lookup } from 'devextreme-react/data-grid'; import { employees, states, cities } from './data.js'; const onEditorPreparing = (e) => { if (e.parentType === 'dataRow' && e.dataField === 'CityID') { const isStateNotSet = e.row.data.StateID === undefined; e.editorOptions.disabled = isStateNotSet; } }; const getFilteredCities = (options) => ({ store: cities, filter: options.data ? ['StateID', '=', options.data.StateID] : null, }); function setStateValue(rowData, value) { rowData.CityID = null; this.defaultSetCellValue(rowData, value); } const App = () => ( <div id="data-grid-demo"> <DataGrid dataSource={employees} keyExpr="ID" showBorders={true} onEditorPreparing={onEditorPreparing} > <Editing mode="row" allowUpdating={true} allowAdding={true} ></Editing> <Column dataField="FirstName" /> <Column dataField="LastName" /> <Column dataField="Position" /> <Column dataField="StateID" caption="State" setCellValue={setStateValue} > <Lookup dataSource={states} displayExpr="Name" valueExpr="ID" /> </Column> <Column dataField="CityID" caption="City" > <Lookup dataSource={getFilteredCities} displayExpr="Name" valueExpr="ID" /> </Column> </DataGrid> </div> ); export default App;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('app'), );
export interface Employee { ID:number; FirstName: string; LastName: string; Prefix: string; Position: string; StateID:number; CityID: number; } export const employees: Employee[] = [{ ID: 1, FirstName: 'John', LastName: 'Heart', Prefix: 'Mr.', Position: 'CTO', StateID: 5, CityID: 17, }, { ID: 2, FirstName: 'Olivia', LastName: 'Peyton', Prefix: 'Mrs.', Position: 'HR Manager', StateID: 5, CityID: 17, }, { ID: 3, FirstName: 'Robert', LastName: 'Reagan', Prefix: 'Mr.', Position: 'IT Manager', StateID: 4, CityID: 14, }, { ID: 4, FirstName: 'Greta', LastName: 'Sims', Prefix: 'Ms.', Position: 'Shipping Manager', StateID: 3, CityID: 8, }, { ID: 5, FirstName: 'Brett', LastName: 'Wade', Prefix: 'Mr.', Position: 'Shipping Manager', StateID: 3, CityID: 9, }, { ID: 6, FirstName: 'Sandra', LastName: 'Johnson', Prefix: 'Mrs.', Position: 'Network Admin', StateID: 2, CityID: 6, }, { ID: 7, FirstName: 'Kevin', LastName: 'Carter', Prefix: 'Mr.', Position: 'Network Admin', StateID: 1, CityID: 3, }, { ID: 8, FirstName: 'Cynthia', LastName: 'Stanwick', Prefix: 'Ms.', Position: 'Sales Assistant', StateID: 1, CityID: 3, }, { ID: 9, FirstName: 'Kent', LastName: 'Samuelson', Prefix: 'Dr.', Position: 'Sales Assistant', StateID: 1, CityID: 2, }, { ID: 10, FirstName: 'Taylor', LastName: 'Riley', Prefix: 'Mr.', Position: 'Support Assistant', StateID: 5, CityID: 17, }, { ID: 11, FirstName: 'Sam', LastName: 'Hill', Prefix: 'Mr.', Position: 'Sales Assistant', StateID: 2, CityID: 5, }, { ID: 12, FirstName: 'Kelly', LastName: 'Rodriguez', Prefix: 'Ms.', Position: 'Sales Assistant', StateID: 5, CityID: 17, }, { ID: 13, FirstName: 'Natalie', LastName: 'Maguirre', Prefix: 'Mrs.', Position: 'Sales Assistant', StateID: 4, CityID: 14, }, { ID: 14, FirstName: 'Walter', LastName: 'Hobbs', Prefix: 'Mr.', Position: 'Support Assistant', StateID: 2, CityID: 5, }]; export const states = [{ ID: 1, Name: 'Alabama', }, { ID: 2, Name: 'Alaska', }, { ID: 3, Name: 'Arizona', }, { ID: 4, Name: 'Arkansas', }, { ID: 5, Name: 'California', }]; export const cities = [{ ID: 1, Name: 'Tuscaloosa', StateID: 1, }, { ID: 2, Name: 'Hoover', StateID: 1, }, { ID: 3, Name: 'Dothan', StateID: 1, }, { ID: 4, Name: 'Decatur', StateID: 1, }, { ID: 5, Name: 'Anchorage', StateID: 2, }, { ID: 6, Name: 'Fairbanks', StateID: 2, }, { ID: 7, Name: 'Juneau', StateID: 2, }, { ID: 8, Name: 'Avondale', StateID: 3, }, { ID: 9, Name: 'Buckeye', StateID: 3, }, { ID: 10, Name: 'Carefree', StateID: 3, }, { ID: 11, Name: 'Springdale', StateID: 4, }, { ID: 12, Name: 'Rogers', StateID: 4, }, { ID: 13, Name: 'Sherwood', StateID: 4, }, { ID: 14, Name: 'Jacksonville', StateID: 4, }, { ID: 15, Name: 'Cabot', StateID: 4, }, { ID: 16, Name: 'Adelanto', StateID: 5, }, { ID: 17, Name: 'Glendale', StateID: 5, }, { ID: 18, Name: 'Moorpark', StateID: 5, }, { ID: 19, Name: 'Needles', StateID: 5, }, { ID: 20, Name: 'Ontario', StateID: 5, }];
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, }, 'openai': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.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/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign', 'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs', 'devextreme-react': 'npm:devextreme-react@link:../../packages/devextreme-react/npm/cjs', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.5/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.54/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/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.12/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'));
export const employees = [ { ID: 1, FirstName: 'John', LastName: 'Heart', Prefix: 'Mr.', Position: 'CTO', StateID: 5, CityID: 17, }, { ID: 2, FirstName: 'Olivia', LastName: 'Peyton', Prefix: 'Mrs.', Position: 'HR Manager', StateID: 5, CityID: 17, }, { ID: 3, FirstName: 'Robert', LastName: 'Reagan', Prefix: 'Mr.', Position: 'IT Manager', StateID: 4, CityID: 14, }, { ID: 4, FirstName: 'Greta', LastName: 'Sims', Prefix: 'Ms.', Position: 'Shipping Manager', StateID: 3, CityID: 8, }, { ID: 5, FirstName: 'Brett', LastName: 'Wade', Prefix: 'Mr.', Position: 'Shipping Manager', StateID: 3, CityID: 9, }, { ID: 6, FirstName: 'Sandra', LastName: 'Johnson', Prefix: 'Mrs.', Position: 'Network Admin', StateID: 2, CityID: 6, }, { ID: 7, FirstName: 'Kevin', LastName: 'Carter', Prefix: 'Mr.', Position: 'Network Admin', StateID: 1, CityID: 3, }, { ID: 8, FirstName: 'Cynthia', LastName: 'Stanwick', Prefix: 'Ms.', Position: 'Sales Assistant', StateID: 1, CityID: 3, }, { ID: 9, FirstName: 'Kent', LastName: 'Samuelson', Prefix: 'Dr.', Position: 'Sales Assistant', StateID: 1, CityID: 2, }, { ID: 10, FirstName: 'Taylor', LastName: 'Riley', Prefix: 'Mr.', Position: 'Support Assistant', StateID: 5, CityID: 17, }, { ID: 11, FirstName: 'Sam', LastName: 'Hill', Prefix: 'Mr.', Position: 'Sales Assistant', StateID: 2, CityID: 5, }, { ID: 12, FirstName: 'Kelly', LastName: 'Rodriguez', Prefix: 'Ms.', Position: 'Sales Assistant', StateID: 5, CityID: 17, }, { ID: 13, FirstName: 'Natalie', LastName: 'Maguirre', Prefix: 'Mrs.', Position: 'Sales Assistant', StateID: 4, CityID: 14, }, { ID: 14, FirstName: 'Walter', LastName: 'Hobbs', Prefix: 'Mr.', Position: 'Support Assistant', StateID: 2, CityID: 5, }, ]; export const states = [ { ID: 1, Name: 'Alabama', }, { ID: 2, Name: 'Alaska', }, { ID: 3, Name: 'Arizona', }, { ID: 4, Name: 'Arkansas', }, { ID: 5, Name: 'California', }, ]; export const cities = [ { ID: 1, Name: 'Tuscaloosa', StateID: 1, }, { ID: 2, Name: 'Hoover', StateID: 1, }, { ID: 3, Name: 'Dothan', StateID: 1, }, { ID: 4, Name: 'Decatur', StateID: 1, }, { ID: 5, Name: 'Anchorage', StateID: 2, }, { ID: 6, Name: 'Fairbanks', StateID: 2, }, { ID: 7, Name: 'Juneau', StateID: 2, }, { ID: 8, Name: 'Avondale', StateID: 3, }, { ID: 9, Name: 'Buckeye', StateID: 3, }, { ID: 10, Name: 'Carefree', StateID: 3, }, { ID: 11, Name: 'Springdale', StateID: 4, }, { ID: 12, Name: 'Rogers', StateID: 4, }, { ID: 13, Name: 'Sherwood', StateID: 4, }, { ID: 14, Name: 'Jacksonville', StateID: 4, }, { ID: 15, Name: 'Cabot', StateID: 4, }, { ID: 16, Name: 'Adelanto', StateID: 5, }, { ID: 17, Name: 'Glendale', StateID: 5, }, { ID: 18, Name: 'Moorpark', StateID: 5, }, { ID: 19, Name: 'Needles', StateID: 5, }, { ID: 20, Name: 'Ontario', StateID: 5, }, ];
<!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.2.3/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>
#data-grid-demo { min-height: 700px; }

This demo shows how to implement cascading lookups:

  1. Configure the primary lookup
    A column's lookup is configured in the lookup object. Assign an array of items to the lookup's dataSource. Then specify the valueExpr and displayExpr properties if the data source contains objects. (See the StateID column.)

  2. Configure the secondary lookup
    The secondary lookup has a similar configuration, but its dataSource should be a function so that you can dynamically filter the lookup. (See step 3.)

  3. Connect the lookups
    To filter the secondary lookup's items based on the primary lookup's value, specify the filter property in the secondary lookup's dataSource. (See the CityID column).

  4. Reset the secondary lookup when the primary lookup's value is changed
    Use the setCellValue function as shown in the StateID column's configuration.

  5. Disable the secondary lookup until the primary lookup's value is set
    Use the onEditorPreparing handler for this.

After you configure lookups, enable edit operations. Specify the required editing.mode and set the editing object's allowUpdating, allowAdding, and allowDeleting properties to true.