mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.1k stars 1.27k forks source link

[data grid] How do I save the filtered row data to state? #13573

Closed MANDT-SYS closed 3 months ago

MANDT-SYS commented 3 months ago

The problem in depth

Thank you for all your help.

I am currently using DataGridPro. I would like to save the results of filtering with GridToolbarQuickFilter and GridToolbarFilterButton set in CustomToolbar to state. How can I do that?

By the way, I read and tried the issue #11729. It was possible to display the filtered data in console.log, but when I save it to state and display it in console.log, I get an infinite loop.

 const [filteredData, setFilteredData] = useState<any[]>([])
  console.log(filteredData)

  const getKeysFromLookup = (obj: any) => {
    const keys = []
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key]) {
        keys.push(key)
      }
    }
    return keys
  }

  const apiRef = useGridApiRef()

  const handleStateChange = useCallback(
    (state: GridState) => {
      const filteredKeys = getKeysFromLookup(state.filter.filteredRowsLookup)
      const rows = filteredKeys.map((key) => apiRef.current.getRow(key))
      console.log('filtered rows', rows)

      setFilteredData(rows)
    },
    [apiRef]
  )

 <DataGridPro
       columns={column}
       rows={dataGridRows}
       slots={{
         toolbar: CustomToolbar,
       }}
       apiRef={apiRef}
       onStateChange={handleStateChange}
 />

Please reply.

Your environment

System: OS: Windows 11 10.0.22631 Binaries: Node: 18.16.0 - C:\Program Files\nodejs\node.EXE npm: 9.5.1 - C:\Program Files\nodejs\npm.CMD pnpm: Not Found Browsers: Chrome: Not Found Edge: Chromium (126.0.2592.61) npmPackages: @emotion/react: ^11.10.4 => 11.10.4 @emotion/styled: ^11.10.4 => 11.10.4 @mui/base: 5.0.0-alpha.100 @mui/core-downloads-tracker: 5.10.8 @mui/icons-material: ^5.10.6 => 5.10.6 @mui/material: ^5.10.8 => 5.10.8 @mui/private-theming: 5.10.6 @mui/styled-engine: 5.10.8 @mui/system: 5.10.8 @mui/types: 7.2.4 @mui/utils: 5.13.1 @mui/x-data-grid: ^5.17.8 => 5.17.8 @mui/x-data-grid-generator: ^6.6.0 => 6.6.0 @mui/x-data-grid-premium: 6.6.0 @mui/x-data-grid-pro: ^6.6.0 => 6.6.0 @mui/x-date-pickers: ^5.0.5 => 5.0.5 @mui/x-date-pickers-pro: ^6.6.0 => 6.6.0 @mui/x-license-pro: 6.6.0 @types/react: ^18.0.18 => 18.0.18 react: ^18.2.0 => 18.2.0 react-dom: ^18.2.0 => 18.2.0 typescript: ^4.8.2 => 4.8.2

Search keywords: data grid filter get Order ID: 68259

michelengelen commented 3 months ago

Hey @MANDT-SYS you are entering an infinite loop because every state change will change the reference to rows and therefore enter the handleStateChange again.

May I ask why you need to store the rows in state?

MANDT-SYS commented 3 months ago

Thank you for your response. I need to perform aggregations on the filtered data and export it to Excel (using ExcelJs for the Excel export).

michelengelen commented 3 months ago

We do support aggregation and export to excel as well... wouldn't that be an option?

MANDT-SYS commented 3 months ago

Thank you for your response.

I am aware that both are feasible with the Premium plan.

However, what I am hoping for is something unique and in a way that is possible within the scope of the Pro plan.

I have the following two ideas for the use of state

  1. displaying aggregated values outside of the data grid
  2. Paste and output the values to a self-made Excel format.

Sorry for the lack of explanation.

michelengelen commented 3 months ago

In that case you can certainly use an external state, but be aware that every state update brings a rerender to the complete data grid. This means you will essentially loose quite a bit of the performance improvements.

The main problem you are facing is that you are updating the state on every state change of the grid. This is why you are running into the infinite loop.

If you need only the filtered rows we do have a getFilterState function that returns the current filterState. Or you can use the handleStateChange function, but extract the data needed and keep the rows state consistent.

I would advise you to do something similar to this:

import * as React from 'react';
import { DataGrid, GridToolbar, useGridApiContext, useGridApiRef } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';
import { Stack } from '@mui/material';

const VISIBLE_FIELDS = ['name', 'rating', 'country', 'salary'];

const AggregatedValues = (props) => {
  const { gridApiRef } = props;

  React.useEffect(() => {
    if (gridApiRef.current == null) {
      return;
    }
    gridApiRef.current.subscribeEvent('filterModelChange', (event) => {
      console.log('filter model change', event);
      console.log(gridApiRef.current.getFilterState(event));
    });
  }, [gridApiRef]);
  return <div>Aggregated values</div>;
};

export default function BasicExampleDataGrid() {
  const apiRef = useGridApiRef();
  const { data } = useDemoData({
    dataSet: 'Employee',
    visibleFields: VISIBLE_FIELDS,
    rowLength: 100,
  });

  const handleStateChange = React.useCallback(
    (newState) => {
      // do something with the state here
    },
    [apiRef],
  );

  return (
    <Stack spacing={2}>
      <div style={{ height: 400, width: '100%' }}>
        <DataGrid
          {...data}
          apiRef={apiRef}
          slots={{ toolbar: GridToolbar }}
          onStateChange={handleStateChange}
        />
      </div>
      <AggregatedValues gridApiRef={apiRef} />
    </Stack>
  );
}

This way you can utilize the grids performant filtering and sorting and on the other hand can access the internal state for outside computations.

Would that be a good starting point for you?

MANDT-SYS commented 3 months ago

Thank you for your response.

I tried to display the code as you provided, but could not run the filter to see how it works.

gridApiRef.current.getFilterState(event) The following error is occurring in the section TypeError: gridApiRef.current.getFilterState is not a function

////////////////////////////////////////////////////////////////////////////////////////////

The code provided was not executable, but I found another solution. When updating the state in the handleStateChange function, it is compared to the original value.

      setFilteredData((prevData) => {
        const prevKeys = prevData.map((row) => row.id)
        const newKeys = rows.map((row) => row.id)

        if (JSON.stringify(prevKeys) ! == JSON.stringify(newKeys)) {
          return rows
        }
        return prevData
      })

By doing this, we were able to save to state without an infinite loop.

michelengelen commented 3 months ago

getFilterState just recently got released, so you would need to use v7.7.1 for it.


your comparison method will work, yes. But I would still strongly advise to use the internal state of the grid for performance reasons.

MANDT-SYS commented 3 months ago

Thank you for your reply.

Understood. Thank you for your help in resolving the issue.

Thank you again.

michelengelen commented 3 months ago

@MANDT-SYS is this issue resolved for you? Or do you need some additional help?

github-actions[bot] commented 3 months ago

:warning: This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue. Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

@MANDT-SYS: How did we do? Your experience with our support team matters to us. If you have a moment, please share your thoughts in this short Support Satisfaction survey.