diff --git a/docs/useList.md b/docs/useList.md index aff6c512d1d..d9085a2a62b 100644 --- a/docs/useList.md +++ b/docs/useList.md @@ -242,3 +242,21 @@ const { refetch, // a function that throws an error, as refetch doesn't make sense for local data } = getGetList({ data }); ``` +## `filterCallback` + +Property for custom filter definition. Being able to apply more complex filters using operators + +```jsx +const { data } = useList({ + data: [ + { id: 1, name: 'Arnold' }, + { id: 2, name: 'Sylvester' }, + { id: 3, name: 'Jean-Claude' }, + ], + sort: { field: 'name', order: 'ASC' }, + filterCallback: (record) => record.id > 1 && record.name !== 'Jean-Claude' +}); +// data will be +// [ +// { id: 2, name: 'Sylvester' }, +// ] \ No newline at end of file diff --git a/packages/ra-core/src/controller/list/useList.spec.tsx b/packages/ra-core/src/controller/list/useList.spec.tsx index 41056bea0c7..7c0c3b6b7bb 100644 --- a/packages/ra-core/src/controller/list/useList.spec.tsx +++ b/packages/ra-core/src/controller/list/useList.spec.tsx @@ -228,4 +228,39 @@ describe('', () => { }) ); }); + + it('should filter array data based on the custom filter', async () => { + const callback = jest.fn(); + const data = [ + { id: 1, items: ['one', 'two'] }, + { id: 2, items: ['three'] }, + { id: 3, items: 'four' }, + { id: 4, items: ['five'] }, + ]; + + render( + record.id > 2} + callback={callback} + /> + ); + + await waitFor(() => { + expect(callback).toHaveBeenCalledWith( + expect.objectContaining({ + sort: { field: 'id', order: 'ASC' }, + isFetching: false, + isLoading: false, + data: [ + { id: 3, items: 'four' }, + { id: 4, items: ['five'] }, + ], + error: undefined, + total: 2, + }) + ); + }); + }); }); diff --git a/packages/ra-core/src/controller/list/useList.ts b/packages/ra-core/src/controller/list/useList.ts index a5b3576edff..fa4bb711486 100644 --- a/packages/ra-core/src/controller/list/useList.ts +++ b/packages/ra-core/src/controller/list/useList.ts @@ -48,6 +48,7 @@ const refetch = () => { * @param {Number} props.page: Optional. The initial page index * @param {Number} props.perPage: Optional. The initial page size * @param {SortPayload} props.sort: Optional. The initial sort (field and order) + * @param {filterCallback} prop.filterCallback Optional. A function that allows you to make a custom filter */ export const useList = ( props: UseListOptions @@ -61,6 +62,7 @@ export const useList = ( page: initialPage = 1, perPage: initialPerPage = 1000, sort: initialSort, + filterCallback = (record: RecordType) => Boolean(record), } = props; const resource = useResourceContext(props); @@ -161,23 +163,25 @@ export const useList = ( // 1. filter if (filterValues) { - tempData = data.filter(record => - Object.entries(filterValues).every( - ([filterName, filterValue]) => { - const recordValue = get(record, filterName); - const result = Array.isArray(recordValue) - ? Array.isArray(filterValue) - ? recordValue.some(item => - filterValue.includes(item) - ) - : recordValue.includes(filterValue) - : Array.isArray(filterValue) - ? filterValue.includes(recordValue) - : filterValue == recordValue; // eslint-disable-line eqeqeq - return result; - } + tempData = data + .filter(record => + Object.entries(filterValues).every( + ([filterName, filterValue]) => { + const recordValue = get(record, filterName); + const result = Array.isArray(recordValue) + ? Array.isArray(filterValue) + ? recordValue.some(item => + filterValue.includes(item) + ) + : recordValue.includes(filterValue) + : Array.isArray(filterValue) + ? filterValue.includes(recordValue) + : filterValue == recordValue; // eslint-disable-line eqeqeq + return result; + } + ) ) - ); + .filter(filterCallback); } const filteredLength = tempData.length; @@ -269,6 +273,7 @@ export interface UseListOptions { perPage?: number; sort?: SortPayload; resource?: string; + filterCallback?: (record: RecordType) => boolean; } export type UseListValue<