mui-x
                                
                                
                                
                                    mui-x copied to clipboard
                            
                            
                            
                        [DataGrid] Add event and callback when filtered rows changes
No easy method to get the current state of visible rows as the filter component updates. the onFilterModelChange function is one step behind the current state of the table - which may be the intended behavior - because it may be a private method.
- [x] The issue is present in the latest release.
 - [x ] I have searched the issues of this repository and believe that this is not a duplicate.
 
Current Behavior 😯
onFilterModelChange one step behind actual state of the table - think this is because its a private method and is actually updating the state of the table.
Expected Behavior 🤔
a method to get the actual visible rows as the filter updates.
Steps to Reproduce 🕹
below is a link to a codesanbox https://codesandbox.io/s/material-demo-forked-nzong?file=/demo.js:867-907
Steps:
- simply filter on quantity
 - watch the console as you type: >= 500000 and see how the visibleRowsLookup is one step behind actual state
 
Context 🔦
notice how it is always one behind actual state...when you look at the console of the params object it is actual correct. this must be an asynchronous action. I would like to be able to access visible rows in real time to update state in another part of the application....imagine if you were updating state in a map (such as google maps and mapbox - my actual scenario - and you may want to style or zoom or visualize other data related to the displayed table in another part of the screen) visualization platform - update state based on any applied filters. you may not want to handle or you may not be able to handle all these updates with getSelectedRows - you may not care about whats selected....just filtered.... I don't believe this is an edge case...this is something that I imagine is going to be needed. if I am missing something...please let me know....I can def be a bonehead....
thanks for the help.
Your Environment 🌎
`npx @material-ui/envinfo`
  Don't forget to mention which browser you used.
  Output from `npx @material-ui/envinfo` goes here.
Order id 💳
I've encountered the same problem: how to get rows after filters were applied?
If I try to use onFilterModelChange and access visibleRows inside the state, they lag behind one update on filters.
Also, being able to access internal state via React ref would be nice:
const ref = React.useRef();
console.log(ref?.state)
<DataGrid ref={ref} />
unfortunately, ref gets passed to the outer div, and not the DataGrid component as a whole.
This way, we could access visible rows outside onFilterModelChange handler, which I think is intended to be used with server-side filtering.
@checkmatez to access the internal state you could use the onStateChange prop.
<DataGrid onStateChange={(param)=> console.log(param)} />
@giq-re: thanks for raising the issue I'm looking into it
(The onStateChange prop is not documented, it's a private API so far, we might break it without any notice.)
see how the
visibleRowsLookupis one step behind actual state
@giq-re As far as I know, visibleRowsLookup is a private cache created from visibleRows (I'm not sure because it's hard to grep in the codebase how the whole state is used, it's fragmented with sub selectors, if we could use full selectors each time, it would be highly appreciated). The value is mutated. It's also affected by #874
(The onStateChange prop is not documented, it's a private API so far, we might break it without any notice.)
I didn't want to touch that. It fires so often. I'll open another issue tomorrow regarding on hover row because of how it fires across cells and I can't figure out how to toggle the trigger off long enough so state doesn't update - I am assuming that the oncellhover is somehow impacting it. currently on remote learning and daddy duty and doing video editing for my wife! And I have a few more but takes time to put together the examples and I don't want to waste your time. Like I just did. Cheers.
I meant toggle it off if you go over a row above or below to go to another part of the screen but don't want the state you updated to change to the row you cross over quickly.
I'll open another issue tomorrow regarding on hover row
@giq-re Yes please, let's keep each issue focused on solving one problem at a time :)
It being private makes sense. Prob being used for server filtering. I assume it's possible for a method similar to getSelectedRows() and/or add visible rows as a property to the onfiltermethod? That would be perfect.
Why was this closed? Was it integrated?
See #1113
We have reverted #1113.
Is this a good example where the 3rd param would make sense ?
onFilterModelChange?: (model: GridFilterModel, event: MuiEvent, params: { visibleRows: GridRow[] })
                                    
                                    
                                    
                                
onFilterModelChange?
@flaviendelangle I would strongly encourage breaking the strong correlation between the filterModel UI state, and the act of filtering the rows client-side: two different things. For example, we could very well filter client-side in an asynchronous fashion (currently it's synchronous, blocking the main thread).
Maybe using https://github.com/mui-org/material-ui-x/issues/1507#issuecomment-828485605 is good enough?
import * as React from "react";
import {
  DataGridPro,
  GridRowId,
  gridVisibleSortedRowIdsSelector
} from "@mui/x-data-grid-pro";
const columns = [
  { field: "id", headerName: "ID", width: 70 },
  { field: "firstName", headerName: "First name", width: 130 }
];
const rows = [
  { id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
  { id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
  { id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
  { id: 4, lastName: "Stark", firstName: "Arya", age: 16 },
  { id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null },
  { id: 6, lastName: "Melisandre", firstName: null, age: 150 },
  { id: 7, lastName: "Clifford", firstName: "Ferrara", age: 44 },
  { id: 8, lastName: "Frances", firstName: "Rossini", age: 36 },
  { id: 9, lastName: "Roxie", firstName: "Harvey", age: 65 }
];
export default function DataGridProDemo() {
  const [visibleRows, setVisibleRows] = React.useState<GridRowId[]>();
  console.log("visibleRows", visibleRows); // Only render when the visible columns changes.
  return (
    <div style={{ height: 400, width: "100%" }}>
      <DataGridPro
        onStateChange={(state) => {
          const newRows = gridVisibleSortedRowIdsSelector(state);
          setVisibleRows(newRows);
        }}
        rows={rows}
        columns={columns}
      />
    </div>
  );
}
https://codesandbox.io/s/material-demo-forked-c2rxb?file=/demo.tsx:0-1418
I was facing the same issue this week resolving a problem in our app. Since we can't use filterModelChange event to get the new filtered rows and since stateChange event triggers for so many different reasons (and therefore frequently too) I've done this:
- I subscribe to the 
filterModelChangeevent and when it triggers I subscribe to thestateChangeevent, because the next trigger of it will be the result of the filter model change - I handle the stateChange event to read the visible rows and immediately unsubscribe from it after
 
useEffect(() => {
    let unsubscribe: () => void;
    const handleStateChange = () => {
        setFilteredRows(gridVisibleSortedRowEntriesSelector(apiRef).map((r) => r.model));
        unsubscribe?.();
    };
    return gridApiRef.current.subscribeEvent?.(
        'filterModelChange',
        () => (unsubscribe = gridApiRef.current.subscribeEvent('stateChange', handleStateChange))
    );
}, [gridApiRef]);
My suggestion
I agree we can't add the visible rows to the filterModelChange even. But we could have better granularity of the stateChange event by either:
- provide individual events that result in 
stateChangeevent i.e.visibleRowsChangeor evenfilterAppliedorsortingApplied - provide a reason in the 
stateChangeevent in theparamsargument where thereasonproperty would define the originating event that caused the state to change iefilterModelChangeorsortModelChangeBy implementing one of these it would make our lives much much easier 
I was facing the same issue this week resolving a problem in our app. Since we can't use
filterModelChangeevent to get the new filtered rows and sincestateChangeevent triggers for so many different reasons (and therefore frequently too) I've done this:
- I subscribe to the
 filterModelChangeevent and when it triggers I subscribe to thestateChangeevent, because the next trigger of it will be the result of the filter model change- I handle the stateChange event to read the visible rows and immediately unsubscribe from it after
 useEffect(() => { let unsubscribe: () => void; const handleStateChange = () => { setFilteredRows(gridVisibleSortedRowEntriesSelector(apiRef).map((r) => r.model)); unsubscribe?.(); }; return gridApiRef.current.subscribeEvent?.( 'filterModelChange', () => (unsubscribe = gridApiRef.current.subscribeEvent('stateChange', handleStateChange)) ); }, [gridApiRef]);My suggestion
I agree we can't add the visible rows to the
filterModelChangeeven. But we could have better granularity of thestateChangeevent by either:
- provide individual events that result in
 stateChangeevent i.e.visibleRowsChangeor evenfilterAppliedorsortingApplied- provide a reason in the
 stateChangeevent in theparamsargument where thereasonproperty would define the originating event that caused the state to change iefilterModelChangeorsortModelChangeBy implementing one of these it would make our lives much much easier
@litera
Hi Robert,
Thank you so much for your recommendation. I encountered a similar issue this week, and your suggestion proved to be very helpful. I did notice that the handleStateChange function was being called twice whenever any changes were made to the filters. However, if the filter was completely cleared, it was only called once.
Although I may not have sufficient expertise to understand the reasons behind this behavior, I did find a solution for the issue. I added a call to unsubscribe?.() at the beginning of the handleStateChange function. Now handleStateChange will only be called once regardless of whether the filters are changing or being cleared.
Once again, I appreciate your assistance and guidance.
Here's the final corrected useEffect
useEffect(() => {
    let unsubscribe: () => void;
    const handleStateChange = () => {
        unsubscribe?.();
        setFilteredRows(gridVisibleSortedRowEntriesSelector(apiRef).map((r) => r.model));
        unsubscribe?.();
    };
    return gridApiRef.current.subscribeEvent?.(
        'filterModelChange',
        () => (unsubscribe = gridApiRef.current.subscribeEvent('stateChange', handleStateChange))
    );
}, [gridApiRef]);
                                    
                                    
                                    
                                
My suggestion
I agree we can't add the visible rows to the
filterModelChangeeven. But we could have better granularity of thestateChangeevent by either:
- provide individual events that result in
 stateChangeevent i.e.visibleRowsChangeor evenfilterAppliedorsortingApplied- provide a reason in the
 stateChangeevent in theparamsargument where thereasonproperty would define the originating event that caused the state to change iefilterModelChangeorsortModelChangeBy implementing one of these it would make our lives much much easier
In my case, I want to deselect any currently selected row if a filter is applied such that a currently selected row is no longer available. The keepNonExistentRowsSelected does not (seem to) work for client-side filtering.
The ideal solution would be a notification when the rows are updated. In the absence of that, one of these suggestions by @litera would go a very long way in making onStateChange usable without needing additional workarounds or taking the hit on processing logic every time onStateChange is called.