ra-core
Version:
Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React
235 lines • 8.43 kB
JavaScript
import { isValidElement, useCallback, useEffect, useMemo } from 'react';
import { useAuthenticated, useRequireAccess } from "../../auth/index.js";
import { useTranslate } from "../../i18n/index.js";
import { useNotify } from "../../notification/index.js";
import { useDataProvider, useGetList, } from "../../dataProvider/index.js";
import { useResourceContext, useGetResourceLabel } from "../../core/index.js";
import { useRecordSelection } from "./useRecordSelection.js";
import { useListParams } from "./useListParams.js";
import { useSelectAll } from "./useSelectAll.js";
import { defaultExporter } from "../../export/index.js";
import { SORT_ASC } from "./queryReducer.js";
import { useEvent } from "../../util/index.js";
/**
* Prepare data for the List view
*
* @param {Object} props The props passed to the List component.
*
* @return {Object} controllerProps Fetched and computed data for the List view
*
* @example
*
* import { useListController } from 'react-admin';
* import ListView from './ListView';
*
* const MyList = props => {
* const controllerProps = useListController(props);
* return <ListView {...controllerProps} {...props} />;
* }
*/
export const useListController = (props = {}) => {
const { debounce = 500, disableAuthentication = false, disableSyncWithLocation = false, exporter = defaultExporter, filter, filterDefaultValues, perPage = 10, queryOptions = {}, sort = defaultSort, storeKey, } = props;
const resource = useResourceContext(props);
const { meta, ...otherQueryOptions } = queryOptions;
if (!resource) {
throw new Error(`useListController requires a non-empty resource prop or context`);
}
if (filter &&
(isValidElement(filter) ||
(Array.isArray(filter) && filter.some(isValidElement)))) {
throw new Error('useListController received a React element as `filter` props. If you intended to set the list filter elements, use the `filters` (with an s) prop instead. The `filter` prop is internal and should not be set by the developer.');
}
const { isPending: isPendingAuthenticated } = useAuthenticated({
enabled: !disableAuthentication,
});
const { isPending: isPendingCanAccess } = useRequireAccess({
action: 'list',
resource,
enabled: !disableAuthentication && !isPendingAuthenticated,
});
const translate = useTranslate();
const notify = useNotify();
const dataProvider = useDataProvider();
const [query, queryModifiers] = useListParams({
debounce,
disableSyncWithLocation,
filterDefaultValues,
perPage,
resource,
sort,
storeKey,
});
const [selectedIds, selectionModifiers] = useRecordSelection({
resource,
disableSyncWithStore: storeKey === false,
storeKey: storeKey === false ? undefined : storeKey,
});
const onUnselectItems = useCallback((fromAllStoreKeys) => {
return selectionModifiers.unselect(selectedIds, fromAllStoreKeys);
}, [selectedIds, selectionModifiers]);
const { data, pageInfo, total, meta: responseMeta, error, isLoading, isFetching, isPending, refetch, isPaused, isPlaceholderData, } = useGetList(resource, {
pagination: {
page: query.page,
perPage: query.perPage,
},
sort: { field: query.sort, order: query.order },
filter: { ...query.filter, ...filter },
meta,
}, {
enabled: (!isPendingAuthenticated && !isPendingCanAccess) ||
disableAuthentication,
placeholderData: previousData => previousData,
retry: false,
onError: error => notify(error?.message || 'ra.notification.http_error', {
type: 'error',
messageArgs: {
_: error?.message,
},
}),
...otherQueryOptions,
});
// change page if there is no data
useEffect(() => {
if (query.page <= 0 ||
(!isFetching &&
query.page > 1 &&
(data == null || data?.length === 0))) {
// Query for a page that doesn't exist, set page to 1
queryModifiers.setPage(1);
return;
}
if (total == null) {
return;
}
const totalPages = Math.ceil(total / query.perPage) || 1;
if (!isFetching && query.page > totalPages) {
// Query for a page out of bounds, set page to the last existing page
// It occurs when deleting the last element of the last page
queryModifiers.setPage(totalPages);
}
}, [isFetching, query.page, query.perPage, data, queryModifiers, total]);
const currentSort = useMemo(() => ({
field: query.sort,
order: query.order,
}), [query.sort, query.order]);
const getResourceLabel = useGetResourceLabel();
const defaultTitle = translate(`resources.${resource}.page.list`, {
_: translate('ra.page.list', {
name: getResourceLabel(resource, 2),
}),
});
const onSelectAll = useSelectAll({
resource,
sort: { field: query.sort, order: query.order },
filter: { ...query.filter, ...filter },
disableSyncWithStore: storeKey === false,
storeKey: storeKey === false ? undefined : storeKey,
});
const getData = useEvent(async ({ maxResults, meta: metaOverride } = {}) => {
if (total === 0) {
return [];
}
const limit = maxResults ?? (total != null ? total : DEFAULT_MAX_RESULTS);
const { data } = await dataProvider.getList(resource, {
sort: currentSort,
filter: filter
? { ...query.filterValues, ...filter }
: query.filterValues,
pagination: { page: 1, perPage: limit },
meta: metaOverride ?? meta,
});
return data;
});
return {
sort: currentSort,
data,
meta: responseMeta,
defaultTitle,
displayedFilters: query.displayedFilters,
error,
exporter,
filter,
filterValues: query.filterValues,
hideFilter: queryModifiers.hideFilter,
isFetching,
isLoading,
isPaused,
isPending,
isPlaceholderData,
onSelect: selectionModifiers.select,
onSelectAll,
onToggleItem: selectionModifiers.toggle,
onUnselectItems,
page: query.page,
perPage: query.perPage,
refetch,
resource,
selectedIds,
setFilters: queryModifiers.setFilters,
setPage: queryModifiers.setPage,
setPerPage: queryModifiers.setPerPage,
setSort: queryModifiers.setSort,
showFilter: queryModifiers.showFilter,
total,
getData,
hasNextPage: pageInfo
? pageInfo.hasNextPage
: total != null
? query.page * query.perPage < total
: undefined,
hasPreviousPage: pageInfo ? pageInfo.hasPreviousPage : query.page > 1,
};
};
const defaultSort = {
field: 'id',
order: SORT_ASC,
};
export const DEFAULT_MAX_RESULTS = 1000;
export const injectedProps = [
'sort',
'data',
'defaultTitle',
'displayedFilters',
'error',
'exporter',
'getData',
'filterValues',
'hasNextPage',
'hasPreviousPage',
'hideFilter',
'isFetching',
'isLoading',
'isPending',
'onSelect',
'onSelectAll',
'onToggleItem',
'onUnselectItems',
'page',
'perPage',
'refetch',
'refresh',
'resource',
'selectedIds',
'setFilters',
'setPage',
'setPerPage',
'setSort',
'showFilter',
'total',
'totalPages',
];
/**
* Select the props injected by the useListController hook
* to be passed to the List children need
* This is an implementation of pick()
*/
export const getListControllerProps = props => injectedProps.reduce((acc, key) => ({ ...acc, [key]: props[key] }), {});
/**
* Select the props not injected by the useListController hook
* to be used inside the List children to sanitize props injected by List
* This is an implementation of omit()
*/
export const sanitizeListRestProps = props => Object.keys(props)
.filter(propName => !injectedProps.includes(propName))
.reduce((acc, key) => ({ ...acc, [key]: props[key] }), {});
//# sourceMappingURL=useListController.js.map