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
JavaScript
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