UNPKG

ra-core

Version:

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

257 lines 9.8 kB
import { useCallback, useEffect, useRef, useState } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import get from 'lodash/get.js'; import isEqual from 'lodash/isEqual.js'; import lodashDebounce from 'lodash/debounce.js'; import { removeEmpty, useEvent } from "../../util/index.js"; import { useDataProvider, useGetManyReference, } from "../../dataProvider/index.js"; import { useNotify } from "../../notification/index.js"; import { DEFAULT_MAX_RESULTS } from "../list/useListController.js"; import usePaginationState from "../usePaginationState.js"; import { useRecordSelection } from "../list/useRecordSelection.js"; import useSortState from "../useSortState.js"; import { useResourceContext } from "../../core/index.js"; import { useRecordContext } from "../record/index.js"; import { defaultExporter } from "../../export/index.js"; /** * Fetch reference records, and return them when available * * Uses dataProvider.getManyReference() internally. * * @example // fetch the comments related to the current post * const { isPending, data } = useReferenceManyFieldController({ * reference: 'comments', * target: 'post_id', * record: { id: 123, title: 'hello, world' }, * resource: 'posts', * }); * * @param {Object} props * @param {string} props.reference The linked resource name. Required. * @param {string} props.target The target resource key. Required. * @param {Object} props.filter The filter applied on the recorded records list * @param {number} props.page the page number * @param {number} props.perPage the number of item per page * @param {Object} props.record The current resource record * @param {string} props.resource The current resource name * @param {Object} props.sort the sort to apply to the referenced records * @param {string} props.source The key of the linked resource identifier * @param {UseQuery Options} props.queryOptions `react-query` options` * * @returns {ListControllerResult} The reference many props */ export const useReferenceManyFieldController = (props) => { const { debounce = 500, reference, target, filter = defaultFilter, exporter = defaultExporter, source = 'id', page: initialPage, perPage: initialPerPage, sort: initialSort = { field: 'id', order: 'DESC' }, queryOptions = {}, } = props; const notify = useNotify(); const record = useRecordContext(props); const resource = useResourceContext(props); const dataProvider = useDataProvider(); const queryClient = useQueryClient(); const { meta, ...otherQueryOptions } = queryOptions; // pagination logic const { page, setPage, perPage, setPerPage } = usePaginationState({ page: initialPage, perPage: initialPerPage, }); // sort logic const { sort, setSort: setSortState } = useSortState(initialSort); const setSort = useCallback((sort) => { setSortState(sort); setPage(1); }, [setPage, setSortState]); // selection logic const [selectedIds, selectionModifiers] = useRecordSelection({ resource: reference, storeKey: props.storeKey ?? `${resource}.${record?.id}.${reference}`, }); const onUnselectItems = useCallback((fromAllStoreKeys) => { return selectionModifiers.unselect(selectedIds, fromAllStoreKeys); }, [selectedIds, selectionModifiers]); // filter logic const filterRef = useRef(filter); const [displayedFilters, setDisplayedFilters] = useState({}); const [filterValues, setFilterValues] = useState(filter); const hideFilter = useCallback((filterName) => { setDisplayedFilters(previousState => { const { [filterName]: _, ...newState } = previousState; return newState; }); setFilterValues(previousState => { const { [filterName]: _, ...newState } = previousState; return newState; }); }, [setDisplayedFilters, setFilterValues]); const showFilter = useCallback((filterName, defaultValue) => { setDisplayedFilters(previousState => ({ ...previousState, [filterName]: true, })); setFilterValues(previousState => ({ ...previousState, [filterName]: defaultValue, })); }, [setDisplayedFilters, setFilterValues]); // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedSetFilters = useCallback(lodashDebounce((filters, displayedFilters) => { setFilterValues(removeEmpty(filters)); setDisplayedFilters(displayedFilters); setPage(1); }, debounce), [setDisplayedFilters, setFilterValues, setPage]); const setFilters = useCallback((filters, displayedFilters, debounce = false) => { if (debounce) { debouncedSetFilters(filters, displayedFilters); } else { setFilterValues(removeEmpty(filters)); setDisplayedFilters(displayedFilters); setPage(1); } }, [setDisplayedFilters, setFilterValues, setPage, debouncedSetFilters]); // handle filter prop change useEffect(() => { if (!isEqual(filter, filterRef.current)) { filterRef.current = filter; setFilterValues(filter); } }, [filter]); const recordValue = get(record, source); const { data, total, meta: responseMeta, pageInfo, error, isFetching, isLoading, isPaused, isPending, isPlaceholderData, refetch, } = useGetManyReference(reference, { target, id: recordValue, pagination: { page, perPage }, sort, filter: filterValues, meta, }, { enabled: recordValue != null, placeholderData: previousData => previousData, onError: error => notify(typeof error === 'string' ? error : error?.message || 'ra.notification.http_error', { type: 'error', messageArgs: { _: typeof error === 'string' ? error : error?.message ? error.message : undefined, }, }), ...otherQueryOptions, }); const onSelectAll = useEvent(async ({ limit = 250, queryOptions = {}, } = {}) => { const { meta, onSuccess, onError } = queryOptions; try { const results = await queryClient.fetchQuery({ queryKey: [ reference, 'getManyReference', { target, id: get(record, source), pagination: { page: 1, perPage: limit }, sort, filter, meta, }, ], queryFn: () => dataProvider.getManyReference(reference, { target, id: get(record, source), pagination: { page: 1, perPage: limit }, sort, filter, meta, }), }); const allIds = results.data?.map(({ id }) => id) || []; selectionModifiers.select(allIds); if (allIds.length === limit) { notify('ra.message.select_all_limit_reached', { messageArgs: { max: limit }, type: 'warning', }); } if (onSuccess) { onSuccess(results); } return results.data; } catch (error) { if (onError) { onError(error); } notify('ra.notification.http_error', { type: 'warning' }); } }); const getData = useEvent(async ({ maxResults, meta: metaOverride } = {}) => { if (recordValue == null || total === 0) { return []; } const limit = maxResults ?? (total != null ? total : DEFAULT_MAX_RESULTS); const { data } = await queryClient.fetchQuery({ queryKey: [ reference, 'getManyReference', { target, id: recordValue, pagination: { page: 1, perPage: limit }, sort, filter: filterValues, meta: metaOverride ?? meta, }, ], queryFn: () => dataProvider.getManyReference(reference, { target, id: recordValue, pagination: { page: 1, perPage: limit }, sort, filter: filterValues, meta: metaOverride ?? meta, }), }); return data; }); return { sort, data, meta: responseMeta, defaultTitle: undefined, displayedFilters, error, exporter, filterValues, hideFilter, isFetching, isLoading, isPaused, isPending, isPlaceholderData, onSelect: selectionModifiers.select, onSelectAll, onToggleItem: selectionModifiers.toggle, onUnselectItems, page, perPage, refetch, resource: reference, selectedIds, setFilters, setPage, setPerPage, hasNextPage: pageInfo ? pageInfo.hasNextPage : total != null ? page * perPage < total : undefined, hasPreviousPage: pageInfo ? pageInfo.hasPreviousPage : page > 1, setSort, showFilter, total, getData, }; }; const defaultFilter = {}; //# sourceMappingURL=useReferenceManyFieldController.js.map