From da5e1f4b3fa262b67513f741c954e0f464ff2fd0 Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Tue, 1 Jun 2021 11:52:25 +0200
Subject: [PATCH 1/4] Introduce useList
---
.../ReferenceArrayFieldController.spec.tsx | 56 ++--
.../field/useReferenceArrayFieldController.ts | 178 +------------
.../ra-core/src/controller/useList.spec.tsx | 171 +++++++++++++
packages/ra-core/src/controller/useList.ts | 240 ++++++++++++++++++
packages/ra-core/src/util/hooks.ts | 2 +-
.../ra-ui-materialui/src/list/ListView.tsx | 3 +-
6 files changed, 459 insertions(+), 191 deletions(-)
create mode 100644 packages/ra-core/src/controller/useList.spec.tsx
create mode 100644 packages/ra-core/src/controller/useList.ts
diff --git a/packages/ra-core/src/controller/field/ReferenceArrayFieldController.spec.tsx b/packages/ra-core/src/controller/field/ReferenceArrayFieldController.spec.tsx
index bcc68489cba..0f688b32cbe 100644
--- a/packages/ra-core/src/controller/field/ReferenceArrayFieldController.spec.tsx
+++ b/packages/ra-core/src/controller/field/ReferenceArrayFieldController.spec.tsx
@@ -200,7 +200,7 @@ describe('', () => {
});
});
- it('should filter string data based on the filter props', () => {
+ it('should filter string data based on the filter props', async () => {
const children = jest.fn().mockReturnValue('child');
renderWithRedux(
', () => {
},
}
);
- expect(children.mock.calls[0][0]).toMatchObject({
- basePath: '/bar',
- currentSort: { field: 'id', order: 'ASC' },
- loaded: true,
- loading: true,
- data: {
- 2: { id: 2, title: 'world' },
- },
- ids: [1, 2],
- error: null,
+ await waitFor(() => {
+ expect(children).toHaveBeenCalledWith(
+ expect.objectContaining({
+ basePath: '/bar',
+ currentSort: { field: 'id', order: 'ASC' },
+ loaded: true,
+ loading: true,
+ data: {
+ 2: { id: 2, title: 'world' },
+ },
+ ids: [2],
+ error: null,
+ })
+ );
});
});
- it('should filter array data based on the filter props', () => {
+ it('should filter array data based on the filter props', async () => {
const children = jest.fn().mockReturnValue('child');
renderWithRedux(
', () => {
},
}
);
- expect(children.mock.calls[0][0]).toMatchObject({
- basePath: '/bar',
- currentSort: { field: 'id', order: 'ASC' },
- loaded: true,
- loading: true,
- data: {
- 1: { id: 1, items: ['one', 'two'] },
- 3: { id: 3, items: 'four' },
- 4: { id: 4, items: ['five'] },
- },
- ids: [1, 2, 3, 4],
- error: null,
+ await waitFor(() => {
+ expect(children).toHaveBeenCalledWith(
+ expect.objectContaining({
+ basePath: '/bar',
+ currentSort: { field: 'id', order: 'ASC' },
+ loaded: true,
+ loading: true,
+ data: {
+ 1: { id: 1, items: ['one', 'two'] },
+ 3: { id: 3, items: 'four' },
+ 4: { id: 4, items: ['five'] },
+ },
+ ids: [1, 3, 4],
+ error: null,
+ })
+ );
});
});
});
diff --git a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts
index 5aa4354be50..2eb48829a0f 100644
--- a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts
+++ b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts
@@ -1,17 +1,11 @@
-import { useCallback, useEffect, useRef } from 'react';
import get from 'lodash/get';
-import isEqual from 'lodash/isEqual';
-import { useSafeSetState, removeEmpty } from '../../util';
-import { Record, RecordMap, Identifier, SortPayload } from '../../types';
+import { Record, SortPayload } from '../../types';
import { useGetMany } from '../../dataProvider';
import { ListControllerProps } from '../useListController';
import { useNotify } from '../../sideEffect';
-import usePaginationState from '../usePaginationState';
-import useSelectionState from '../useSelectionState';
-import useSortState from '../useSortState';
import { useResourceContext } from '../../core';
-import { indexById } from '../../util/indexById';
+import { useList } from '../useList';
interface Option {
basePath?: string;
@@ -92,173 +86,27 @@ const useReferenceArrayFieldController = (
}
);
- const [loadingState, setLoadingState] = useSafeSetState(loading);
- const [loadedState, setLoadedState] = useSafeSetState(loaded);
-
- const [finalData, setFinalData] = useSafeSetState(
- indexById(data)
- );
- const [finalIds, setFinalIds] = useSafeSetState(ids);
-
- // pagination logic
- const { page, setPage, perPage, setPerPage } = usePaginationState({
- page: initialPage,
- perPage: initialPerPage,
- });
-
- // sort logic
- const { sort, setSort: setSortObject } = useSortState(initialSort);
- const setSort = useCallback(
- (field: string, order: string = 'ASC') => {
- setSortObject({ field, order });
- setPage(1);
- },
- [setPage, setSortObject]
- );
-
- // selection logic
- const {
- selectedIds,
- onSelect,
- onToggleItem,
- onUnselectItems,
- } = useSelectionState();
-
- // filter logic
- const filterRef = useRef(filter);
- const [displayedFilters, setDisplayedFilters] = useSafeSetState<{
- [key: string]: boolean;
- }>({});
- const [filterValues, setFilterValues] = useSafeSetState<{
- [key: string]: any;
- }>(filter);
- const hideFilter = useCallback(
- (filterName: string) => {
- setDisplayedFilters(previousState => {
- const { [filterName]: _, ...newState } = previousState;
- return newState;
- });
- setFilterValues(previousState => {
- const { [filterName]: _, ...newState } = previousState;
- return newState;
- });
- },
- [setDisplayedFilters, setFilterValues]
- );
- const showFilter = useCallback(
- (filterName: string, defaultValue: any) => {
- setDisplayedFilters(previousState => ({
- ...previousState,
- [filterName]: true,
- }));
- setFilterValues(previousState => ({
- ...previousState,
- [filterName]: defaultValue,
- }));
- },
- [setDisplayedFilters, setFilterValues]
- );
- const setFilters = useCallback(
- (filters, displayedFilters) => {
- setFilterValues(removeEmpty(filters));
- setDisplayedFilters(displayedFilters);
- setPage(1);
- },
- [setDisplayedFilters, setFilterValues, setPage]
- );
- // handle filter prop change
- useEffect(() => {
- if (!isEqual(filter, filterRef.current)) {
- filterRef.current = filter;
- setFilterValues(filter);
- }
- });
-
- // We do all the data processing (filtering, sorting, paginating) client-side
- useEffect(() => {
- if (!loaded) return;
- // 1. filter
- let tempData = data.filter(record =>
- Object.entries(filterValues).every(([filterName, filterValue]) =>
- Array.isArray(get(record, filterName))
- ? get(record, filterName).includes(filterValue)
- : // eslint-disable-next-line eqeqeq
- filterValue == get(record, filterName)
- )
- );
- // 2. sort
- if (sort.field) {
- tempData = tempData.sort((a, b) => {
- if (get(a, sort.field) > get(b, sort.field)) {
- return sort.order === 'ASC' ? 1 : -1;
- }
- if (get(a, sort.field) < get(b, sort.field)) {
- return sort.order === 'ASC' ? -1 : 1;
- }
- return 0;
- });
- }
- // 3. paginate
- tempData = tempData.slice((page - 1) * perPage, page * perPage);
- setFinalData(indexById(tempData));
- setFinalIds(
- tempData
- .filter(data => typeof data !== 'undefined')
- .map(data => data.id)
- );
- }, [
- data,
- filterValues,
+ const listProps = useList({
+ error,
+ filter,
+ initialData: data,
+ initialIds: ids,
+ initialPage,
+ initialPerPage,
+ initialSort,
+ loading,
loaded,
- page,
- perPage,
- setFinalData,
- setFinalIds,
- sort.field,
- sort.order,
- ]);
-
- useEffect(() => {
- if (loaded !== loadedState) {
- setLoadedState(loaded);
- }
- }, [loaded, loadedState, setLoadedState]);
-
- useEffect(() => {
- if (loading !== loadingState) {
- setLoadingState(loading);
- }
- }, [loading, loadingState, setLoadingState]);
+ });
return {
basePath: basePath
? basePath.replace(resource, reference)
: `/${reference}`,
- currentSort: sort,
- data: finalData,
+ ...listProps,
defaultTitle: null,
- error,
- displayedFilters,
- filterValues,
hasCreate: false,
- hideFilter,
- ids: finalIds,
- loaded: loadedState,
- loading: loadingState,
- onSelect,
- onToggleItem,
- onUnselectItems,
- page,
- perPage,
refetch,
resource: reference,
- selectedIds,
- setFilters,
- setPage,
- setPerPage,
- setSort,
- showFilter,
- total: finalIds.length,
};
};
diff --git a/packages/ra-core/src/controller/useList.spec.tsx b/packages/ra-core/src/controller/useList.spec.tsx
new file mode 100644
index 00000000000..673f39b88a1
--- /dev/null
+++ b/packages/ra-core/src/controller/useList.spec.tsx
@@ -0,0 +1,171 @@
+import * as React from 'react';
+import expect from 'expect';
+
+import { useList, UseListOptions, UseListValue } from './useList';
+import { render, waitFor } from '@testing-library/react';
+
+const UseList = ({
+ callback,
+ ...props
+}: UseListOptions & { callback: (value: UseListValue) => void }) => {
+ const value = useList(props);
+ callback(value);
+ return null;
+};
+
+describe('', () => {
+ it('should filter string data based on the filter props', () => {
+ const callback = jest.fn();
+ const data = [
+ { id: 1, title: 'hello' },
+ { id: 2, title: 'world' },
+ ];
+ const ids = [1, 2];
+
+ render(
+
+ );
+
+ expect(callback).toHaveBeenCalledWith(
+ expect.objectContaining({
+ currentSort: { field: 'id', order: 'ASC' },
+ loaded: true,
+ loading: true,
+ data: {
+ 2: { id: 2, title: 'world' },
+ },
+ ids: [2],
+ error: undefined,
+ })
+ );
+ });
+
+ it('should filter array data based on the filter props', async () => {
+ const callback = jest.fn();
+ const data = [
+ { id: 1, items: ['one', 'two'] },
+ { id: 2, items: ['three'] },
+ { id: 3, items: 'four' },
+ { id: 4, items: ['five'] },
+ ];
+ const ids = [1, 2, 3, 4];
+
+ render(
+
+ );
+
+ await waitFor(() => {
+ expect(callback).toHaveBeenCalledWith(
+ expect.objectContaining({
+ currentSort: { field: 'id', order: 'ASC' },
+ loaded: true,
+ loading: true,
+ data: {
+ 1: { id: 1, items: ['one', 'two'] },
+ 3: { id: 3, items: 'four' },
+ 4: { id: 4, items: ['five'] },
+ },
+ ids: [1, 3, 4],
+ error: undefined,
+ })
+ );
+ });
+ });
+
+ it('should apply sorting correctly', async () => {
+ const callback = jest.fn();
+ const data = [
+ { id: 1, title: 'hello' },
+ { id: 2, title: 'world' },
+ ];
+ const ids = [1, 2];
+
+ render(
+
+ );
+
+ await waitFor(() => {
+ expect(callback).toHaveBeenCalledWith(
+ expect.objectContaining({
+ currentSort: { field: 'title', order: 'DESC' },
+ loaded: true,
+ loading: true,
+ data: {
+ 2: { id: 2, title: 'world' },
+ 1: { id: 1, title: 'hello' },
+ },
+ ids: [2, 1],
+ error: undefined,
+ })
+ );
+ });
+ });
+
+ it('should apply pagination correctly', async () => {
+ const callback = jest.fn();
+ const data = [
+ { id: 1, title: 'hello' },
+ { id: 2, title: 'world' },
+ { id: 3, title: 'baz' },
+ { id: 4, title: 'bar' },
+ { id: 5, title: 'foo' },
+ { id: 6, title: 'plop' },
+ { id: 7, title: 'bazinga' },
+ ];
+ const ids = [1, 2, 3, 4, 5, 6, 7];
+
+ render(
+
+ );
+
+ await waitFor(() => {
+ expect(callback).toHaveBeenCalledWith(
+ expect.objectContaining({
+ currentSort: { field: 'id', order: 'ASC' },
+ loaded: true,
+ loading: true,
+ data: {
+ 6: { id: 6, title: 'plop' },
+ 7: { id: 7, title: 'bazinga' },
+ },
+ ids: [6, 7],
+ page: 2,
+ perPage: 5,
+ error: undefined,
+ })
+ );
+ });
+ });
+});
diff --git a/packages/ra-core/src/controller/useList.ts b/packages/ra-core/src/controller/useList.ts
new file mode 100644
index 00000000000..ee1fecb319a
--- /dev/null
+++ b/packages/ra-core/src/controller/useList.ts
@@ -0,0 +1,240 @@
+import { useCallback, useEffect, useRef } from 'react';
+import get from 'lodash/get';
+import isEqual from 'lodash/isEqual';
+import { indexById, removeEmpty, useSafeSetState } from '../util';
+import { Identifier, Record, RecordMap, SortPayload } from '../types';
+import usePaginationState from './usePaginationState';
+import useSortState from './useSortState';
+import useSelectionState from './useSelectionState';
+import { ListControllerProps } from '.';
+
+/**
+ * Hook that applies list filtering, sorting and pagination on the provided data, either in memory or through the provided function.
+ *
+ * @example
+ * const data = [
+ * { id: 1, name: 'Arnold' },
+ * { id: 2, name: 'Sylvester' },
+ * { id: 3, name: 'Jean-Claude' },
+ * ]
+ * const { ids, data, error, loaded, loading } = useList({
+ * ids: providedIds,
+ * data: providedData,
+ * basePath: '/resource';
+ * resource: 'resource';
+ * });
+ *
+ * @param {Object} props
+ * @param {string} props.basePath basepath to current resource
+ * @param {string} props.resource The current resource name
+ */
+export const useList = (props: UseListOptions): UseListValue => {
+ const {
+ data,
+ error,
+ filter = defaultFilter,
+ ids,
+ initialData,
+ initialIds,
+ loaded,
+ loading,
+ initialPage = 1,
+ initialPerPage = 1000,
+ initialSort = defaultSort,
+ total,
+ } = props;
+ const [loadingState, setLoadingState] = useSafeSetState(loading);
+ const [loadedState, setLoadedState] = useSafeSetState(loaded);
+
+ const [finalItems, setFinalItems] = useSafeSetState<{
+ data: RecordMap;
+ ids: Identifier[];
+ }>(() => ({
+ data: indexById(initialData),
+ ids: initialIds,
+ }));
+
+ // pagination logic
+ const { page, setPage, perPage, setPerPage } = usePaginationState({
+ page: initialPage,
+ perPage: initialPerPage,
+ });
+
+ // sort logic
+ const { sort, setSort: setSortObject } = useSortState(initialSort);
+ const setSort = useCallback(
+ (field: string, order = 'ASC') => {
+ setSortObject({ field, order });
+ setPage(1);
+ },
+ [setPage, setSortObject]
+ );
+
+ // selection logic
+ const {
+ selectedIds,
+ onSelect,
+ onToggleItem,
+ onUnselectItems,
+ } = useSelectionState();
+
+ // filter logic
+ const filterRef = useRef(filter);
+ const [displayedFilters, setDisplayedFilters] = useSafeSetState<{
+ [key: string]: boolean;
+ }>({});
+ const [filterValues, setFilterValues] = useSafeSetState<{
+ [key: string]: any;
+ }>(filter);
+ const hideFilter = useCallback(
+ (filterName: string) => {
+ setDisplayedFilters(previousState => {
+ const { [filterName]: _, ...newState } = previousState;
+ return newState;
+ });
+ setFilterValues(previousState => {
+ const { [filterName]: _, ...newState } = previousState;
+ return newState;
+ });
+ },
+ [setDisplayedFilters, setFilterValues]
+ );
+ const showFilter = useCallback(
+ (filterName: string, defaultValue: any) => {
+ setDisplayedFilters(previousState => ({
+ ...previousState,
+ [filterName]: true,
+ }));
+ setFilterValues(previousState => ({
+ ...previousState,
+ [filterName]: defaultValue,
+ }));
+ },
+ [setDisplayedFilters, setFilterValues]
+ );
+ const setFilters = useCallback(
+ (filters, displayedFilters) => {
+ setFilterValues(removeEmpty(filters));
+ setDisplayedFilters(displayedFilters);
+ setPage(1);
+ },
+ [setDisplayedFilters, setFilterValues, setPage]
+ );
+ // handle filter prop change
+ useEffect(() => {
+ if (!isEqual(filter, filterRef.current)) {
+ filterRef.current = filter;
+ setFilterValues(filter);
+ }
+ });
+
+ // We do all the data processing (filtering, sorting, paginating) client-side
+ useEffect(() => {
+ if (!loaded) return;
+ // 1. filter
+ let tempData = initialData.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;
+ })
+ );
+ // 2. sort
+ if (sort.field) {
+ tempData = tempData.sort((a, b) => {
+ if (get(a, sort.field) > get(b, sort.field)) {
+ return sort.order === 'ASC' ? 1 : -1;
+ }
+ if (get(a, sort.field) < get(b, sort.field)) {
+ return sort.order === 'ASC' ? -1 : 1;
+ }
+ return 0;
+ });
+ }
+ // 3. paginate
+ tempData = tempData.slice((page - 1) * perPage, page * perPage);
+ const finalData = indexById(tempData);
+ const finalIds = tempData
+ .filter(data => typeof data !== 'undefined')
+ .map(data => data.id);
+
+ setFinalItems({
+ data: finalData,
+ ids: finalIds,
+ });
+ }, [
+ initialData,
+ filterValues,
+ loaded,
+ page,
+ perPage,
+ setFinalItems,
+ sort.field,
+ sort.order,
+ ]);
+
+ useEffect(() => {
+ if (loaded !== loadedState) {
+ setLoadedState(loaded);
+ }
+ }, [loaded, loadedState, setLoadedState]);
+
+ useEffect(() => {
+ if (loading !== loadingState) {
+ setLoadingState(loading);
+ }
+ }, [loading, loadingState, setLoadingState]);
+
+ return {
+ currentSort: props.currentSort || sort,
+ data: data || finalItems.data,
+ error,
+ displayedFilters: props.displayedFilters || displayedFilters,
+ filterValues: props.filterValues || filterValues,
+ hideFilter: props.hideFilter || hideFilter,
+ ids: ids || finalItems.ids,
+ loaded: loadedState,
+ loading: loadingState,
+ onSelect: props.onSelect || onSelect,
+ onToggleItem: props.onToggleItem || onToggleItem,
+ onUnselectItems: props.onUnselectItems || onUnselectItems,
+ page: props.page || page,
+ perPage: props.perPage || perPage,
+ selectedIds: props.selectedIds || selectedIds,
+ setFilters: props.setFilters || setFilters,
+ setPage: props.setPage || setPage,
+ setPerPage: props.setPerPage || setPerPage,
+ setSort: props.setSort || setSort,
+ showFilter: props.showFilter || showFilter,
+ total: total || finalItems.ids.length,
+ };
+};
+
+export interface UseListOptions
+ extends Partial<
+ Omit
+ > {
+ error?: any;
+ filter?: any;
+ initialPage?: number;
+ initialPerPage?: number;
+ initialSort?: SortPayload;
+ initialData: Record[];
+ initialIds: Identifier[];
+ loaded: boolean;
+ loading: boolean;
+}
+
+export type UseListValue = Omit<
+ ListControllerProps,
+ 'resource' | 'basePath' | 'refetch'
+>;
+
+const defaultFilter = {};
+const defaultSort = { field: null, order: null };
diff --git a/packages/ra-core/src/util/hooks.ts b/packages/ra-core/src/util/hooks.ts
index a27cc268595..83c717b7878 100644
--- a/packages/ra-core/src/util/hooks.ts
+++ b/packages/ra-core/src/util/hooks.ts
@@ -5,7 +5,7 @@ import isEqual from 'lodash/isEqual';
// thanks Kent C Dodds for the following helpers
export function useSafeSetState(
- initialState?: T
+ initialState?: T | (() => T)
): [T, React.Dispatch>] {
const [state, setState] = useState(initialState);
diff --git a/packages/ra-ui-materialui/src/list/ListView.tsx b/packages/ra-ui-materialui/src/list/ListView.tsx
index d84d8f97ce9..47c269aa3b8 100644
--- a/packages/ra-ui-materialui/src/list/ListView.tsx
+++ b/packages/ra-ui-materialui/src/list/ListView.tsx
@@ -194,7 +194,8 @@ const useStyles = makeStyles(
export interface ListViewProps
extends Omit,
- ListControllerProps {
+ // Partial because we now get those props via context
+ Partial {
children: ReactElement;
}
From 90cd17529bbfb7a450a1400dd8b148f6df2ad72d Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Wed, 2 Jun 2021 11:27:00 +0200
Subject: [PATCH 2/4] Add comments
---
packages/ra-core/src/controller/useList.ts | 42 +++++++++++++++++-----
1 file changed, 33 insertions(+), 9 deletions(-)
diff --git a/packages/ra-core/src/controller/useList.ts b/packages/ra-core/src/controller/useList.ts
index ee1fecb319a..2583e391d0d 100644
--- a/packages/ra-core/src/controller/useList.ts
+++ b/packages/ra-core/src/controller/useList.ts
@@ -17,16 +17,34 @@ import { ListControllerProps } from '.';
* { id: 2, name: 'Sylvester' },
* { id: 3, name: 'Jean-Claude' },
* ]
- * const { ids, data, error, loaded, loading } = useList({
- * ids: providedIds,
- * data: providedData,
- * basePath: '/resource';
- * resource: 'resource';
- * });
*
- * @param {Object} props
- * @param {string} props.basePath basepath to current resource
- * @param {string} props.resource The current resource name
+ * const MyComponent = () => {
+ * const listContext = useList({
+ * ids: providedIds,
+ * data: providedData,
+ * basePath: '/resource';
+ * resource: 'resource';
+ * });
+ * return (
+ *
+ *
+ *
+ *
+ *
+ *
+ * );
+ * };
+ *
+ * @param {UseListOptions} props Also optionally accepts all the ListController props
+ * @param {Record[]} props.data An array of records
+ * @param {Identifier[]} props.ids An array of the record identifiers
+ * @param {Boolean} props.loaded: A boolean indicating whether the data has been loaded at least once
+ * @param {Boolean} props.loading: A boolean indicating whether the data is being loaded
+ * @param {Error | String} props.error: Optional. The error if any occured while loading the data
+ * @param {Object} props.filter: Optional. An object containing the filters applied on the data
+ * @param {Number} props.initialPage: Optional. The initial page index
+ * @param {Number} props.initialPerPage: Optional. The initial page size
+ * @param {SortPayload} props.initialSort: Optional. The initial sort (field and order)
*/
export const useList = (props: UseListOptions): UseListValue => {
const {
@@ -131,6 +149,11 @@ export const useList = (props: UseListOptions): UseListValue => {
// We do all the data processing (filtering, sorting, paginating) client-side
useEffect(() => {
if (!loaded) return;
+ // Assume that if setFilters is provided then so are methods for pagination and sorting
+ if (props.setFilters) {
+ return;
+ }
+
// 1. filter
let tempData = initialData.filter(record =>
Object.entries(filterValues).every(([filterName, filterValue]) => {
@@ -174,6 +197,7 @@ export const useList = (props: UseListOptions): UseListValue => {
loaded,
page,
perPage,
+ props.setFilters,
setFinalItems,
sort.field,
sort.order,
From 67e1e69fcc0b5c9453daea39d567af1d815835a8 Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Wed, 2 Jun 2021 11:27:26 +0200
Subject: [PATCH 3/4] export useList
---
packages/ra-core/src/controller/index.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/ra-core/src/controller/index.ts b/packages/ra-core/src/controller/index.ts
index 56432180237..d2a8f71f597 100644
--- a/packages/ra-core/src/controller/index.ts
+++ b/packages/ra-core/src/controller/index.ts
@@ -65,3 +65,4 @@ export * from './button';
export * from './details';
export * from './RecordContext';
export * from './saveModifiers';
+export * from './useList';
From 3623e9c20ad1701ef176bf524413fce497c58a70 Mon Sep 17 00:00:00 2001
From: Gildas Garcia <1122076+djhi@users.noreply.github.com>
Date: Wed, 2 Jun 2021 14:28:12 +0200
Subject: [PATCH 4/4] Fix filters handling
---
packages/ra-core/src/controller/useList.ts | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/packages/ra-core/src/controller/useList.ts b/packages/ra-core/src/controller/useList.ts
index 2583e391d0d..b72db06bd24 100644
--- a/packages/ra-core/src/controller/useList.ts
+++ b/packages/ra-core/src/controller/useList.ts
@@ -123,17 +123,21 @@ export const useList = (props: UseListOptions): UseListValue => {
...previousState,
[filterName]: true,
}));
- setFilterValues(previousState => ({
- ...previousState,
- [filterName]: defaultValue,
- }));
+ setFilterValues(previousState =>
+ removeEmpty({
+ ...previousState,
+ [filterName]: defaultValue,
+ })
+ );
},
[setDisplayedFilters, setFilterValues]
);
const setFilters = useCallback(
(filters, displayedFilters) => {
setFilterValues(removeEmpty(filters));
- setDisplayedFilters(displayedFilters);
+ if (displayedFilters) {
+ setDisplayedFilters(displayedFilters);
+ }
setPage(1);
},
[setDisplayedFilters, setFilterValues, setPage]