I'm using the free version of the MUI Data Grid component and I'm not able to get filtering and sorting to work correctly with a column that renders multiple values in an array. In this example, my multiple-value array column is 'tags', which shows some categories for words in a data grid.
The desired functionality is the following:
Filtering: I want the user to be able to choose a tag from a dropdown list and have the table show only the rows that contain that tag. It would also be acceptable to have the user select several tags and match on all, but it's not necessary. The expected filter menu would be something like: "Column: Tags, Operator: Contains, Value: [One or several selected tags from a list of all possible tags]".
Sorting: This is more open to interpretation, but at a minimum, it should sort all rows containing tags before/after rows that contain no tags.
Below is what I have so far, but I believe it's not working because the 'singleSelect' column type is expecting a single string value to compare against instead of an array of strings. Looking through the MUI documentation, however, I'm not able to find a corresponding column/filter option that would match a predefined value in a dropdown list against any one of the values in a corresponding string array column.
This is an example of the filtering not working as expected (the expected behavior is that when the user selects 'vehicles', the table displays only rows 3, 6, & 10):
Here you can see sorting has no effect:
MUI documentation where I looked for a solution:
CodePudding user response:
You need to set custom sortComparator
and filterOperators
props to the related column definition like this:
import {
DataGrid,
getGridSingleSelectOperators,
GridCellParams,
GridColDef,
GridComparatorFn,
GridFilterItem,
GridToolbar
} from "@mui/x-data-grid"
import { Box, Chip, Stack } from "@mui/material"
const tagsSortComparator: GridComparatorFn<any> = (tags1: any, tags2: any) => {
return tags1.length - tags2.length
}
const tagsFilterOperators = getGridSingleSelectOperators()
.filter((operator) => operator.value === "isAnyOf")
.map((operator) => {
const newOperator = { ...operator }
const newGetApplyFilterFn = (filterItem: GridFilterItem, column: GridColDef) => {
return (params: GridCellParams): boolean => {
let isOk = true
filterItem?.value?.forEach((fv: any) => {
isOk = isOk && params.value.includes(fv)
})
return isOk
}
}
newOperator.getApplyFilterFn = newGetApplyFilterFn
return newOperator
})
export default function App() {
const rows = [
{ id: 1, german: "der Kuchen", english: "cake", tags: ["food", "baked goods", "kitchen"] },
{ id: 2, german: "rot", english: "red", tags: ["colors"] },
{ id: 3, german: "das Auto", english: "car", tags: ["vehicles", "garage"] },
{ id: 4, german: "fliegend", english: "flying", tags: [] },
{ id: 5, german: "grün", english: "green", tags: ["colors", "nature"] },
{ id: 6, german: "der Hubschrauber", english: "helicopter", tags: ["vehicles"] },
{ id: 7, german: "die Gabel", english: "fork", tags: ["kitchen"] },
{ id: 8, german: "das Hemd", english: "shirt", tags: ["clothes", "closet"] },
{ id: 9, german: "tatsächlich", english: "actual", tags: [] },
{ id: 10, german: "der Bus", english: "bus", tags: ["school", "vehicles"] }
]
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 30, filterable: false },
{ field: "german", headerName: "German", width: 150 },
{ field: "english", headerName: "English", width: 100 },
{
field: "tags",
headerName: "Tags",
width: 300,
type: "singleSelect",
valueOptions: [...new Set(rows.map((o) => o.tags).flat())],
renderCell: (params) => (
<Stack direction="row" spacing={0.25}>
{params.row.tags.map((tag: string) => (
<Chip label={tag} />
))}
</Stack>
),
sortComparator: tagsSortComparator,
filterOperators: tagsFilterOperators
}
]
return (
<Box sx={{ height: 500, width: "100%" }}>
<DataGrid
rows={rows}
columns={columns}
density={"compact"}
disableSelectionOnClick
components={{
Toolbar: GridToolbar
}}
/>
</Box>
)
}
You can take a look at this sandbox for a live working example of this solution.