Nju
Nju

Reputation: 639

Mui/DataGrid set/get filter value in Custom filter component

How can I in mui/DataGrid custoim filter set value which will be seen in onFilterModelChange?

I have code:

function MyFilterPanel() {
  const apiRef = useGridApiContext();
 
  const handleDateChange = () => {
    const { state, setState, forceUpdate } = apiRef.current;
    setState({
        filterModel: {
          items: [{ columnField: 'quantity', operatorValue: '>', value: 10000 }],
        },
    });
  };
  return (
    <DateRangePicker
      onChange={handleDateChange}
    />
  );
}
 // and
<DataGrid
    ...
        filterMode="server"
        onFilterModelChange={(newValue) => {
            console.log(newValue);
        }}
        components={{
            FilterPanel: MyFilterPanel,
        }}
/>

I have got error:

TypeError: Cannot read properties of undefined (reading 'columnVisibilityModel')

EDIT. I added my code to show how I want to use it. https://codesandbox.io/s/datagrid-forked-2ulvnu

How to use that:

  1. Click on ...
  2. Pick Filter
  3. Pick date range
import * as React from "react";
import DateRangePicker from "rsuite/DateRangePicker";
import { DataGrid, useGridApiContext } from "@mui/x-data-grid";
import "./ui.css"; // @import "rsuite/dist/rsuite.css";

function DateRangePickerFilterPanel() {
  const apiRef = useGridApiContext();

  const handleDateChange = (value) => {
    // I want set here values for previous and next date
  };
  return <DateRangePicker onChange={handleDateChange} />;
}

const rows = [
  { id: "2020-01-02", col1: "Hello" },
  { id: "2020-01-03", col1: "MUI X" },
  { id: "2020-01-04", col1: "Material UI" },
  { id: "2020-01-05", col1: "MUI" },
  { id: "2020-01-06", col1: "Joy UI" },
  { id: "2020-01-07", col1: "MUI Base" }
];

const columns = [
  { field: "id", headerName: "DateTime", type: "dateTime", width: 150 },
  { field: "col1", headerName: "Column 1", width: 150 }
];

export default function App() {
  const [dates, setDates] = React.useState({});

  /*
  const getAllNames = () => {
    axiosInstance.get(`${API_PATH}/api/names?startDate=${dates.startDate}&endDate=${dates.endDate}`)
      .then((response) => {
        ...
      })
      .catch((error) => console.error(`Error: ${error}`));
  };
  */
  return (
    <div style={{ height: 300, width: "100%" }}>
      <DataGrid
        rows={rows}
        pagination
        columns={columns}
        paginationMode="server"
        rowsPerPageOptions={[10]}
        filterMode="server"
        onFilterModelChange={(newValue) => {
          // And here I want to get that data to be able
          // to pas it to backend request or somehow have acces
          // to it in fucntion App()
          console.log(newValue);
          // setDates({
          //   startDate: newValue[0],
          //   endDate: newValue[1].toISOString()
          // });
        }}
        components={{
          FilterPanel: DateRangePickerFilterPanel
        }}
      />
    </div>
  );
}

Upvotes: 3

Views: 11016

Answers (2)

Ezra
Ezra

Reputation: 830

UPDATE: Given the clarification from OP that they are NOT using MUI DataGridPro and are using server-side filtering, here is a solution:

If I understand you correctly, it looks like you are trying to accomplish the following:

  1. Capture the start and end dates from the rsuite/DateRangePicker,
  2. Use the server-side filtering in DataGrid to filter the results

For #1, we can capture the date values OUTSIDE of the DateRangePickerFilterPanel by passing the onChange handler function in as a prop. You can use a custom filter panel and pass it the handler function as a prop using the components and componentsProps attributes of DataGrid.

function DateRangePickerFilterPanel(props) {
  // Instead of handling a date change here, pass the handler in as a prop.
  // This allows you access to the selected values in App. You could also use
  // a state management library like redux, but that is not shown here.
  return <DateRangePicker onChange={props.onChange} />;
}

export default function App() {
  const [dates, setDates] = useState()

  const handleDateChange = (newValue) => {
    // update local state with the new values
    setDates(newValue);
  }

  return (
    <DataGrid
      rows={rows}       // defined elsewhere
      columns={columns} // defined elsewhere
      components={{
        FilterPanel: DateRangePickerFilterPanel,
      }}
      componentsProps={{
        filterPanel: { onChange: handleDateChange }
      }}
    >
    </DataGrid>
  );
}

Second, we want to call the server whenever the filter dates are updated. We are storing the ‘dates’ in react state and we can use a useEffect hook to call the server every time those dates are updated

export default function App() {
  const [dates, setDates] = useState()

  useEffect( () => {
    // call the server here
  }, [dates]);
}

NOTE: the server-side documentation here indicates that you need to use the onFilterModelChange handler, but that is not necessary in this case since you are using a custom filter panel. We can trigger off of the update of the DateRangePicker and we do not need to use the onFilterModelChange.

Here is the full solution with comments:

import * as React from "react";
import { DateRangePicker } from "rsuite";
import { DataGrid, GridFilterModel } from "@mui/x-data-grid";
import "./ui.css";
import { fakeAxios } from "./server";

function DateRangePickerFilterPanel(props) {
  // Instead of handling a date change here, pass the handler in as a prop.
  // This allows you access to the selected values in App. You could also use
  // a state management library like redux, but that is not shown here.
  return <DateRangePicker onChange={props.onChange} />;
}

const columns = [
  { field: "id", headerName: "ID", width: 150 },
  { field: "created", headerName: "DateTime", type: "date", width: 150 },
  { field: "col1", headerName: "Column 1", width: 150 }
];

export default function App() {
  // These are the selected values in the date range picker. To use server
  // side filtering they must be sent to the server, and the server returns
  // the filtered dataset.
  const [dates, setDates] = React.useState({});

  // Store the row data for the data table in react state. This will be updated
  // when you call the server API with filter parameters.
  const [rows, setRows] = React.useState([]);

  // Here is where we handle the date change in the filter panel. Set the dates
  // state so it can be used by the server API.
  const handleDateChange = (newValue) => {
    setDates(newValue);
  };

  // The rows for the datatable are loaded from the server using the dates as
  // a filter. This useEffect runs (and calls the server) every time the value
  // of 'dates' changes.
  React.useEffect(() => {
    fakeAxios
      .get(`/api/names?startDate=${dates[0]}&endDate=${dates[1]}`)
      .then((response) => {
        console.log(
          `server called with filter; returned ${response.length} records`
        );
        setRows(response);
      });
  }, [dates]);

  return (
    <div style={{ height: 500, width: "100%" }}>
      <DataGrid
        rows={rows}
        pagination
        columns={columns}
        paginationMode="server"
        rowCount={10}
        rowsPerPageOptions={[10, 100]}
        filterMode="server"
        
        // onFilterModelChange is not needed since we are using a custom filter
        // panel.

        components={{
          FilterPanel: DateRangePickerFilterPanel
        }}
        componentsProps={{
          filterPanel: { onChange: handleDateChange }
        }}
      />
    </div>
  );
}

I am mocking the server response with the 'fakeAxios' object. It does not filter the data as it would in the actual server and instead just returns a random number of records.

See the full code sandbox for more detail here: https://codesandbox.io/s/datagrid-forked-version2-koz9cy?file=/src/App.tsx


Original Answer:

tl;dr

  1. GridApi is a pro feature - use DataGridPro instead of DataGrid
  2. use the useGridApiRef() hook (not the useGridApiContext() hook) to access the GridApi from outside the Grid component

The main issue here is that GridApi is a pro/premium feature that will work on DataGridPro, but not DataGrid. The documentation isn't super clear about this (by their own admission here: https://github.com/mui/mui-x/issues/2904#issuecomment-945436602). In the API docs for DataGrid the apiRef property is not available, but it is there on DataGridPro.

The 2nd issue is that you should be using useGridApiRef() not useGridApiContext(). Basically, useGridApiRef() is for accessing the GridApi from outside the data grid, while useGridApiContext() is used to access the GRidApi from within the data grid (they provide a detailed explanation here)

Here is the code that accomplishes what you are looking for:

import { useState } from "react";
import { DataGridPro, useGridApiRef } from "@mui/x-data-grid-pro";
import { DateRangePicker, LocalizationProvider } from "@mui/x-date-pickers-pro";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import { AdapterDayjs } from "@mui/x-date-pickers-pro/AdapterDayjs";
import "./styles.css";

export default function App() {
  const [value, setValue] = useState([null, null]);
  const gridApi = useGridApiRef();

  const rows = [
    { id: 1, col1: "Hello", col2: "World", quantity: 5000 },
    { id: 2, col1: "DataGridPro", col2: "is Awesome", quantity: 5000 },
    { id: 3, col1: "MUI", col2: "is Amazing", quantity: 12000 }
  ];

  const columns = [
    { field: "col1", headerName: "Column 1", width: 150 },
    { field: "col2", headerName: "Column 2", width: 150 },
    { field: "quantity", headerName: "Quantity", width: 150, type: "number" }
  ];

  const handleDateChange = (newValue) => {
    setValue(newValue);

    if (gridApi.current) {
      gridApi.current.setFilterModel({
        items: [
          {
            columnField: "quantity",
            operatorValue: ">",
            value: "10000"
          }
        ]
      });
    }
  };

  return (
    <div className="App" style={{ height: "300px" }}>
      <h1>Hello CodeSandbox</h1>
      <LocalizationProvider dateAdapter={AdapterDayjs}>
        <DateRangePicker
          value={value}
          onChange={handleDateChange}
          renderInput={(startProps, endProps) => (
            <>
              <TextField {...startProps} />
              <Box sx={{ mx: 2 }}> to </Box>
              <TextField {...endProps} />
            </>
          )}
        ></DateRangePicker>
      </LocalizationProvider>
      <DataGridPro
        apiRef={gridApi}
        rows={rows}
        columns={columns}
        onFilterModelChange={(newValue) => {
          console.log(`received filter mode change: ${newValue}`);
          console.log(newValue);
        }}
      ></DataGridPro>
    </div>
  );
}

Code Sandbox here: https://codesandbox.io/s/stackoverflow-mui-datagrid-ehxesp

Upvotes: 5

Prashant Jangam
Prashant Jangam

Reputation: 2888

I am assuming that you want to get the dates from the DateRangePicker and pass them to your API call. Once user selects the dateRange then again re-populate the datagrid(assuming based on filterMode set to server). I have updated your code. please check. As you are not using Mui's FilterModel you don't need onFilterModelChange prop.

import DateRangePicker from "rsuite/DateRangePicker";
import { DataGrid} from "@mui/x-data-grid";
import "./ui.css";

const getAllNames = (startDate, endDate)=> {
  console.log(startDate, endDate);
 //  Add your API call here
};

function DateRangePickerFilterPanel() {
 const handleDateChange = (value) => {
   getAllNames(value[0], value[1]);
 };
 return <DateRangePicker onChange={handleDateChange} />;
};

const rows = [
  { id: "2020-01-02", col1: "Hello" },
  { id: "2020-01-03", col1: "MUI X" },
  { id: "2020-01-04", col1: "Material UI" },
  { id: "2020-01-05", col1: "MUI" },
  { id: "2020-01-06", col1: "Joy UI" },
  { id: "2020-01-07", col1: "MUI Base" }
];

const columns = [
  { field: "id", headerName: "DateTime", type: "dateTime", width: 150 },
  { field: "col1", headerName: "Column 1", width: 150 }
];

export default function App() {
  /*
  const getAllNames = () => {
    axiosInstance.get(`${API_PATH}/api/names?startDate=${dates.startDate}&endDate=${dates.endDate}`)
      .then((response) => {
        ...
      })
      .catch((error) => console.error(`Error: ${error}`));
  };
  */
 
  return (
    <div style={{ height: 300, width: "100%" }}>
      <DataGrid
        rows={rows}
        pagination
        columns={columns}
        paginationMode="server"
        rowsPerPageOptions={[10]}
        filterMode="server"
        components={{
          FilterPanel: DateRangePickerFilterPanel
        }}
      />
    </div>
  );
}

Upvotes: -1

Related Questions