React Grid with Dynamic Dropdown and Selection

A React grid code example with dynamic dropdown and selection.

React Dynamic Grid

Overview

In this article let’s go back to the basics of Javascript and React programming. I am going to develop a React application that does the followings,

  • Use faker.js to generate faker data.
  • Display the data in a grid using AG Grid.
  • Create dropdowns with cascading select for countries, sub countries, and cities. E.g., when I select the United Kingdom, then sub country dropdown should only show England, Northern Ireland, Scotland, and Wales.
  • Only display the check box for modified rows.

Generate Fake Data

For application development or testing, you always need data for testing. Libraries like Faker.js for Javascript and Faker for Python come in handy for this need.

Using Faker.js, I can easily generate persons’ details as shown in the below code snippet.

import faker from "faker";useEffect(() => {
async function loadData() {
const persons = [];
for (var i = 0; i < 1000; i++) {
var person = {};
person.name = faker.name.findName();
person.email = faker.internet.email();
person.country = "";
person.subcountry = "";
person.city = "";

persons.push(person);
}
setData(persons);
}
loadData();
}, []);
Generated Persons’ Details

AG Grid

Using AG Grid, I can then display the data easily.

<div className="ag-theme-alpine" style={{
height: 600, marginLeft: 20, marginRight: 20, marginTop: 10
}}>
<AgGridReact
onGridReady={onGridReady}
rowData={data}
columnDefs={columnDefs}
suppressRowClickSelection={true}
pagination={true}
paginationPageSize={10}
enableRangeSelection={true}
paginationNumberFormatter={
function(params) { return '[' + params.value.toLocaleString() + ']'; }}
rowSelection={'multiple'}
sizeColumnsToFit={true}
enableCellTextSelection={true}
frameworkComponents={frameworkComponents}
isRowSelectable={
function(rowNode) { return false; }}
/>

</div>

For country, sub-country, and city selection, I need to use cell renderers.

For world cities, I am going to use the dataset here.

The dataset is saved in a JSON file. The Javascript is straightforward. Basically, I just need to build a dictionary to lookup up the corresponding sub countries and cities based on countries and sub countries.

const cities = require('../data/cities.json');
const LOOKUP = {};
const COUNTRIES = [];
cities.forEach(function(city) {// Country to subcountries
if (LOOKUP[city.country]) {
const subcountries = LOOKUP[city.country];
if (!subcountries.includes(city.subcountry)) {
subcountries.push(city.subcountry);
}
} else {
COUNTRIES.push(city.country);
const subcountries = [];
subcountries.push(city.subcountry);
LOOKUP[city.country] = subcountries;
}
// Subcountry to cities
if (LOOKUP[city.subcountry]) {
const cities = LOOKUP[city.subcountry];
if (!cities.includes(city.name)) {
cities.push(city.name);
}
} else {
const cities = [];
cities.push(city.name);
LOOKUP[city.subcountry] = cities;
}
});
COUNTRIES.sort();Object.keys(LOOKUP).forEach(function(key) {
const values = LOOKUP[key];
values.sort();
});

I also need to create 3 cell renderers for country, sub-country, and city.

When a dropdown value is selected, it searches in the lookup dictionary on the corresponding child values and then refreshes the next dropdowns.

Also when the value changes, the changed field will be set to true to indicate that the row has been changed.

function setDirty(props) {
if (props.data.changed) return;
props.data.changed = true;
const rows = [];
rows.push(props.node);
props.node.selectable = true;
props.api.redrawRows({ rowNodes: rows });
}
export function CountryRenderer(props) {const onChangeCountry = (e) => {
setDirty(props);
props.data.country = e.target.value;
props.data.subcountry = "";
props.data.city = "";
props.api.refreshCells({ columns: ['subcountry', 'city'], force: true });
};
return (
<div>
<select defaultValue={props.data.country} onChange={(e) => onChangeCountry(e)}>
<option key=""></option>
{COUNTRIES.map(country =>
<option key={country}>{country}</option>
)}
</select>
</div>
)
}
export function SubcountryRenderer(props) {
var country = props.data.country;
var subcountries = [""];
if (country) {
subcountries = LOOKUP[country];
}
const onChangeSubcountry = (e) => {
setDirty(props);
props.data.subcountry = e.target.value;
props.data.city = "";
props.api.refreshCells({ columns: ['city'], force: true });
};
return (
<div>
<select defaultValue={props.data.subcountry} onChange={(e) => onChangeSubcountry(e)}>
<option key=""></option>
{subcountries.map(subcountry =>
<option key={subcountry}>{subcountry}</option>
)}
</select>
</div>
)
}
export function CityRenderer(props) {
var subcountry = props.data.subcountry;
var cities = [""];
if (subcountry) {
cities = LOOKUP[subcountry];
}
const onChangeCity = (e) => {
setDirty(props);
props.data.city = e.target.value;
};
return (
<div>
<select defaultValue={props.data.city} onChange={(e) => onChangeCity(e)}>
<option key=""></option>
{cities.map(city =>
<option key={city}>{city}</option>
)}
</select>
</div>
)
}

For the column definitions of the grid, the code detects if the row has been changed and shall display the check box accordingly.

const columnDefs = [{
field: "row_id",
headerName: "#",
checkboxSelection: function(params) {
if (params.data.changed) {
return true;
}
return false;
},
width: 50,
headerCheckboxSelection: true,
pinned: 'left'
},

{ field: "name", headerName: "Name", filter: true, sortable: true, resizable: true, floatingFilter: true, pinned: 'left' },
{ field: "email", headerName: "Email", filter: true, sortable: true, resizable: true, floatingFilter: true },
{
field: "country",
headerName: "Country",
resizable: true,
cellRenderer: "CountryRenderer",
},
{
field: "subcountry",
headerName: "Subcountry",
resizable: true,
cellRenderer: "SubcountryRenderer",
},
{
field: "city",
headerName: "City",
resizable: true,
cellRenderer: "CityRenderer",
},
];

And here is the output of this simple application.

Dynamic Grid

The code I used can be found in this repository.

References

Programmer and occasional blogger.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store