2

I'm using the free version of the MUI X 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.

Code Sandbox

import { DataGrid, GridColDef, GridToolbar } from "@mui/x-data-grid"
import { Box, Chip, Stack } from "@mui/material"

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>
      )
    }
  ]

  return (
    <Box sx={{ height: 500, width: "100%" }}>
      <DataGrid
        rows={rows}
        columns={columns}
        density={"compact"}
        disableSelectionOnClick
        components={{
          Toolbar: GridToolbar
        }}
      />
    </Box>
  )
}

This is what the data grid looks like before trying to filter it: data-grid

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): data-grid-no-filter-results

Here you can see sorting has no effect: data-grid-sort-not-working

MUI documentation where I looked for a solution:

3 Answers 3

6

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 stackblitz for a live working example of this solution.

Sign up to request clarification or add additional context in comments.

1 Comment

Ahmet, this solved my problem perfectly, thanks so much for the assistance and great example.
1

Going off the accepted answer - if you need to display tags, but your valueOptions are a {value, label} type of object, you will need to set a valueGetter in your column definition along with the filterOperators above. Something like:

valueGetter: (params) => params?.row?.pathToYourValue

The accepted solution saved a lot of headache, I was just missing the valueGetter to fit my use case :)

1 Comment

valueGetter made sorting work without it the sortComperator gets undefined value to compare and sort
0

const statusOrder = { pending: 0, accepted: 1, rejected: 2 };

in columns: sortComparator: (v1, v2) => statusOrder[v1] - statusOrder[v2], valueGetter : (a, b, c, d)=>{ return b.approval; }

the column in my case contains strings

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.