Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .changeset/quiet-otters-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"@refinedev/mui": patch
---

feat: add `filters.syncFilterModel` option to `useDataGrid`

`useDataGrid` controls `dataGridProps.filterModel` from an internal state that
was only updated by the DataGrid's own filter panel and by `search()`. As a
result, calls to the returned `setFilters` (or filter changes synced from the
URL via `syncWithLocation`) updated `filters` but left `filterModel` stale, so
external filter UIs could not drive the grid's visible filter state.

Add an opt-in `filters.syncFilterModel` flag. When `true`, the hook reflects
`filters` changes in `dataGridProps.filterModel`. The default is `false` to
preserve compatibility with apps that mix column-header filtering and
external filter inputs — see #5860 for the original reasoning behind the
controlled-but-unsynced behavior.

```tsx
const { dataGridProps } = useDataGrid({
resource: "posts",
filters: {
syncFilterModel: true,
},
});
```
72 changes: 72 additions & 0 deletions packages/mui/src/hooks/useDataGrid/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,78 @@ describe("useDataGrid Hook", () => {
});
});

it("when filters.syncFilterModel is true, external setFilters updates dataGridProps.filterModel", async () => {
const { result } = renderHook(
() =>
useDataGrid({
resource: "posts",
filters: {
syncFilterModel: true,
},
}),
{
wrapper: TestWrapper({}),
},
);

await waitFor(() => {
expect(!result.current.tableQuery?.isLoading).toBeTruthy();
});

await act(async () => {
result.current.setFilters([
{
field: "title",
operator: "contains",
value: "test",
},
]);
});

await waitFor(() => {
expect(result.current.dataGridProps.filterModel?.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "title",
value: "test",
}),
]),
);
});
});

it("by default, external setFilters does not update dataGridProps.filterModel", async () => {
const { result } = renderHook(
() =>
useDataGrid({
resource: "posts",
}),
{
wrapper: TestWrapper({}),
},
);

await waitFor(() => {
expect(!result.current.tableQuery?.isLoading).toBeTruthy();
});

const initialItems = result.current.dataGridProps.filterModel?.items;

await act(async () => {
result.current.setFilters([
{
field: "title",
operator: "contains",
value: "test",
},
]);
});

expect(result.current.dataGridProps.filterModel?.items).toEqual(
initialItems,
);
});

it("should not change sortModel when page changes", async () => {
const { result } = renderHook(
() =>
Expand Down
17 changes: 17 additions & 0 deletions packages/mui/src/hooks/useDataGrid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ export type UseDataGridProps<
* @default "replace"
*/
defaultBehavior?: "replace" | "merge";
/**
* When `true`, changes to `filters` made outside the DataGrid (via the
* returned `setFilters`, `search`, or `syncWithLocation`) are reflected
* in `dataGridProps.filterModel`. Defaults to `false` to preserve
* compatibility with apps that mix DataGrid column-header filtering and
* external filter inputs, where forcing a sync would clobber in-progress
* header edits.
* @default false
*/
syncFilterModel?: boolean;
}
>;
editable?: boolean;
Expand Down Expand Up @@ -200,6 +210,13 @@ export function useDataGrid<

const [muiCrudFilters, setMuiCrudFilters] = useState<CrudFilters>(filters);

const syncFilterModel = filtersFromProp?.syncFilterModel ?? false;

useEffect(() => {
if (!syncFilterModel) return;
setMuiCrudFilters((prev) => (isEqual(prev, filters) ? prev : filters));
}, [filters, syncFilterModel]);

const { data, isFetched, isLoading } = tableQuery;

const rowCountRef = useRef(data?.total || 0);
Expand Down
Loading