UNPKG

ra-core

Version:

Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React

290 lines (272 loc) 8.63 kB
import { isValidElement, useEffect, useMemo } from 'react'; import { UseQueryOptions } from 'react-query'; import { useAuthenticated } from '../../auth'; import { useTranslate } from '../../i18n'; import { useNotify } from '../../notification'; import { useGetList, UseGetListHookValue } from '../../dataProvider'; import { SORT_ASC } from './queryReducer'; import { defaultExporter } from '../../export'; import { FilterPayload, SortPayload, RaRecord, Exporter } from '../../types'; import { useResourceContext, useGetResourceLabel } from '../../core'; import { useRecordSelection } from './useRecordSelection'; import { useListParams } from './useListParams'; /** * 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 = <RecordType extends RaRecord = any>( props: ListControllerProps<RecordType> = {} ): ListControllerResult<RecordType> => { const { debounce = 500, disableAuthentication, disableSyncWithLocation, exporter = defaultExporter, filter, filterDefaultValues, perPage = 10, queryOptions = {}, sort = defaultSort, storeKey, } = props; useAuthenticated({ enabled: !disableAuthentication }); const resource = useResourceContext(props); const { meta, ...otherQueryOptions } = queryOptions; if (!resource) { throw new Error( `<List> was called outside of a ResourceContext and without a resource prop. You must set the resource prop.` ); } if (filter && isValidElement(filter)) { throw new Error( '<List> 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 translate = useTranslate(); const notify = useNotify(); const [query, queryModifiers] = useListParams({ debounce, disableSyncWithLocation, filterDefaultValues, perPage, resource, sort, storeKey, }); const [selectedIds, selectionModifiers] = useRecordSelection(resource); const { data, pageInfo, total, error, isLoading, isFetching, refetch, } = useGetList<RecordType>( resource, { pagination: { page: query.page, perPage: query.perPage, }, sort: { field: query.sort, order: query.order }, filter: { ...query.filter, ...filter }, meta, }, { keepPreviousData: true, 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('ra.page.list', { name: getResourceLabel(resource, 2), }); return { sort: currentSort, data, defaultTitle, displayedFilters: query.displayedFilters, error, exporter, filter, filterValues: query.filterValues, hideFilter: queryModifiers.hideFilter, isFetching, isLoading, onSelect: selectionModifiers.select, onToggleItem: selectionModifiers.toggle, onUnselectItems: selectionModifiers.clearSelection, 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: pageInfo ? pageInfo.hasNextPage : total != null ? query.page * query.perPage < total : undefined, hasPreviousPage: pageInfo ? pageInfo.hasPreviousPage : query.page > 1, }; }; export interface ListControllerProps<RecordType extends RaRecord = any> { debounce?: number; disableAuthentication?: boolean; /** * Whether to disable the synchronization of the list parameters with the current location (URL search parameters) */ disableSyncWithLocation?: boolean; exporter?: Exporter | false; filter?: FilterPayload; filterDefaultValues?: object; perPage?: number; queryOptions?: UseQueryOptions<{ data: RecordType[]; total?: number; pageInfo?: { hasNextPage?: boolean; hasPreviousPage?: boolean; }; }> & { meta?: any }; resource?: string; sort?: SortPayload; storeKey?: string | false; } const defaultSort = { field: 'id', order: SORT_ASC, }; export interface ListControllerResult<RecordType extends RaRecord = any> { sort: SortPayload; data: RecordType[]; defaultTitle?: string; displayedFilters: any; error?: any; exporter?: Exporter | false; filter?: FilterPayload; filterValues: any; hideFilter: (filterName: string) => void; isFetching: boolean; isLoading: boolean; onSelect: (ids: RecordType['id'][]) => void; onToggleItem: (id: RecordType['id']) => void; onUnselectItems: () => void; page: number; perPage: number; refetch: (() => void) | UseGetListHookValue<RecordType>['refetch']; resource: string; selectedIds: RecordType['id'][]; setFilters: ( filters: any, displayedFilters: any, debounce?: boolean ) => void; setPage: (page: number) => void; setPerPage: (page: number) => void; setSort: (sort: SortPayload) => void; showFilter: (filterName: string, defaultValue: any) => void; total: number; hasNextPage: boolean; hasPreviousPage: boolean; } export const injectedProps = [ 'sort', 'data', 'defaultTitle', 'displayedFilters', 'error', 'exporter', 'filterValues', 'hideFilter', 'isFetching', 'isLoading', 'onSelect', '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] }), {});