ra-core
Version:
Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React
176 lines • 7.04 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, useInfiniteGetList } from "../../dataProvider/index.js";
import { defaultExporter } from "../../export/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 { useEvent } from "../../util/index.js";
/**
* Prepare data for the InfiniteList view
*
* @param {Object} props The props passed to the InfiniteList component.
*
* @return {Object} controllerProps Fetched and computed data for the List view
*
* @example
*
* import { useInfiniteListController } from 'react-admin';
* import ListView from './ListView';
*
* const MyList = props => {
* const controllerProps = useInfiniteListController(props);
* return <ListView {...controllerProps} {...props} />;
* }
*/
export const useInfiniteListController = (props = {}) => {
const { debounce = 500, disableAuthentication = false, disableSyncWithLocation = false, exporter = defaultExporter, filter, filterDefaultValues, perPage = 10, queryOptions, sort, storeKey, } = props;
const resource = useResourceContext(props);
const { meta, ...otherQueryOptions } = queryOptions ?? {};
if (!resource) {
throw new Error(`<InfiniteList> was called outside of a ResourceContext and without a resource prop. You must set the resource prop.`);
}
if (filter && isValidElement(filter)) {
throw new Error('<InfiniteList> 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 });
const onUnselectItems = useCallback((fromAllStoreKeys) => {
return selectionModifiers.unselect(selectedIds, fromAllStoreKeys);
}, [selectedIds, selectionModifiers]);
const { data, total, error, isLoading, isPaused, isPending, isPlaceholderData, isFetching, hasNextPage, hasPreviousPage, fetchNextPage, isFetchingNextPage, fetchPreviousPage, isFetchingPreviousPage, refetch, meta: responseMeta, } = useInfiniteGetList(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,
});
const onSelectAll = useSelectAll({
resource,
sort: { field: query.sort, order: query.order },
filter: { ...query.filter, ...filter },
});
// change page if there is no data
useEffect(() => {
if (query.page <= 0 ||
(!isFetching &&
query.page > 1 &&
(data == null || data?.pages.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 unwrappedData = useMemo(() => data?.pages?.reduce((acc, page) => [...acc, ...page.data], []), [data]);
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: unwrappedData,
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: total,
hasNextPage,
hasPreviousPage,
fetchNextPage,
isFetchingNextPage,
fetchPreviousPage,
isFetchingPreviousPage,
meta: responseMeta,
getData,
};
};
const DEFAULT_MAX_RESULTS = 1000;
//# sourceMappingURL=useInfiniteListController.js.map