diff --git a/packages/ra-core/src/controller/field/ReferenceManyFieldController.tsx b/packages/ra-core/src/controller/field/ReferenceManyFieldController.tsx index 4f70b49be02..f6b8adc9a3b 100644 --- a/packages/ra-core/src/controller/field/ReferenceManyFieldController.tsx +++ b/packages/ra-core/src/controller/field/ReferenceManyFieldController.tsx @@ -1,20 +1,11 @@ -import { ReactNode, useState, useReducer, useEffect, useMemo } from 'react'; -// @ts-ignore -import { useSelector, useDispatch } from 'react-redux'; -import get from 'lodash/get'; +import { ReactElement, FunctionComponent } from 'react'; -import { crudGetManyReference } from '../../actions'; import { SORT_ASC, SORT_DESC, } from '../../reducer/admin/resource/list/queryReducer'; -import { - getIds, - getReferences, - getTotal, - nameRelatedTo, -} from '../../reducer/admin/references/oneToMany'; import { Record, Sort, RecordMap, Identifier, Dispatch } from '../../types'; +import useReferenceMany from './useReferenceMany'; interface ChildrenFuncParams { currentSort: Sort; @@ -32,11 +23,8 @@ interface ChildrenFuncParams { interface Props { basePath: string; - children: (params: ChildrenFuncParams) => ReactNode; - data?: RecordMap; + children: (params: ChildrenFuncParams) => ReactElement; filter?: any; - ids?: any[]; - loadedOnce?: boolean; perPage?: number; record?: Record; reference: string; @@ -58,8 +46,6 @@ const sortReducer = (state: Sort, field: string | Sort): Sort => { return { field, order }; }; -const defaultFilter = {}; - /** * Render related records to the current one. * @@ -106,79 +92,47 @@ const defaultFilter = {}; * ... * */ -export const ReferenceManyFieldController = ({ +export const ReferenceManyFieldController: FunctionComponent = ({ resource, reference, record, target, - filter = defaultFilter, + filter, source, - children, basePath, - perPage = 25, - sort = { field: 'id', order: 'DESC' }, + perPage, + sort, + children, }) => { - const referenceId = get(record, source); - const relatedTo = useMemo( - () => nameRelatedTo(reference, referenceId, resource, target, filter), - [filter, reference, referenceId, resource, target] - ); - const ids = useSelector(selectIds(relatedTo), [relatedTo]); - const data = useSelector(selectData(reference, relatedTo), [ - reference, - relatedTo, - ]); - const total = useSelector(selectTotal(relatedTo), [relatedTo]); - const [page, setPage] = useState(1); - const [currentPerPage, setPerPage] = useState(perPage); - useEffect(() => setPerPage(perPage), [perPage]); - const [currentSort, setSort] = useReducer(sortReducer, sort); - useEffect(() => setSort(sort), [sort.field, sort.order]); - - const dispatch = useDispatch(); - - useEffect( - fetchReferences({ - reference, - referenceId, - resource, - target, - filter, - source, - page, - perPage: currentPerPage, - sort: currentSort, - dispatch, - }), - [ - reference, - referenceId, - resource, - target, - filter, - source, - crudGetManyReference, - page, - currentPerPage, - currentSort.field, - currentSort.order, - ] - ); - - const referenceBasePath = basePath.replace(resource, reference); - - return children({ + const { + currentSort, + data, + ids, + loadedOnce, + page, + currentPerPage, + referenceBasePath, + setPage, + setPerPage, + setSort, + total, + } = useReferenceMany({ + resource, reference, record, - resource, target, filter, source, + basePath, + perPage, sort, + }); + + return children({ currentSort, data, ids, - loadedOnce: typeof ids !== 'undefined', + loadedOnce, page, perPage: currentPerPage, referenceBasePath, @@ -189,44 +143,4 @@ export const ReferenceManyFieldController = ({ }); }; -const fetchReferences = ({ - reference, - referenceId, - resource, - target, - filter, - source, - dispatch, - page, - perPage, - sort, -}) => () => { - const relatedTo = nameRelatedTo( - reference, - referenceId, - resource, - target, - filter - ); - - dispatch( - crudGetManyReference( - reference, - target, - referenceId, - relatedTo, - { page, perPage }, - sort, - filter, - source - ) - ); -}; - -const selectData = (reference, relatedTo) => state => - getReferences(state, reference, relatedTo); - -const selectIds = relatedTo => state => getIds(state, relatedTo); -const selectTotal = relatedTo => state => getTotal(state, relatedTo); - export default ReferenceManyFieldController; diff --git a/packages/ra-core/src/controller/field/useReferenceMany.ts b/packages/ra-core/src/controller/field/useReferenceMany.ts new file mode 100644 index 00000000000..87f50f5e3bd --- /dev/null +++ b/packages/ra-core/src/controller/field/useReferenceMany.ts @@ -0,0 +1,177 @@ +import { ReactNode, useState, useReducer, useEffect, useMemo } from 'react'; +// @ts-ignore +import { useSelector, useDispatch } from 'react-redux'; +import get from 'lodash/get'; + +import { crudGetManyReference } from '../../actions'; +import { + SORT_ASC, + SORT_DESC, +} from '../../reducer/admin/resource/list/queryReducer'; +import { + getIds, + getReferences, + getTotal, + nameRelatedTo, +} from '../../reducer/admin/references/oneToMany'; +import { Record, Sort, RecordMap, Identifier, Dispatch } from '../../types'; + +interface ChildrenFuncParams { + currentSort: Sort; + data: RecordMap; + ids: Identifier[]; + loadedOnce: boolean; + page: number; + currentPerPage: number; + referenceBasePath: string; + setPage: (page: number) => void; + setPerPage: (perPage: number) => void; + setSort: (field: string) => void; + total: number; +} + +interface Options { + basePath: string; + data?: RecordMap; + filter?: any; + ids?: any[]; + loadedOnce?: boolean; + perPage?: number; + record?: Record; + reference: string; + resource: string; + sort?: Sort; + source: string; + target: string; + total?: number; +} + +const sortReducer = (state: Sort, field: string | Sort): Sort => { + if (typeof field !== 'string') { + return field; + } + const order = + state.field === field && state.order === SORT_ASC + ? SORT_DESC + : SORT_ASC; + return { field, order }; +}; + +const defaultFilter = {}; + +const useReferenceMany = ({ + resource, + reference, + record, + target, + filter = defaultFilter, + source, + basePath, + perPage = 25, + sort = { field: 'id', order: 'DESC' }, +}: Options): ChildrenFuncParams => { + const referenceId = get(record, source); + const relatedTo = useMemo( + () => nameRelatedTo(reference, referenceId, resource, target, filter), + [filter, reference, referenceId, resource, target] + ); + const ids = useSelector(selectIds(relatedTo), [relatedTo]); + const data = useSelector(selectData(reference, relatedTo), [ + reference, + relatedTo, + ]); + const total = useSelector(selectTotal(relatedTo), [relatedTo]); + const [page, setPage] = useState(1); + const [currentPerPage, setPerPage] = useState(perPage); + useEffect(() => setPerPage(perPage), [perPage]); + const [currentSort, setSort] = useReducer(sortReducer, sort); + useEffect(() => setSort(sort), [sort.field, sort.order]); + + const dispatch = useDispatch(); + + useEffect( + fetchReferences({ + reference, + referenceId, + resource, + target, + filter, + source, + page, + perPage: currentPerPage, + sort: currentSort, + dispatch, + }), + [ + reference, + referenceId, + resource, + target, + filter, + source, + crudGetManyReference, + page, + currentPerPage, + currentSort.field, + currentSort.order, + ] + ); + + const referenceBasePath = basePath.replace(resource, reference); + + return { + currentSort, + data, + ids, + loadedOnce: typeof ids !== 'undefined', + page, + currentPerPage, + referenceBasePath, + setPage, + setPerPage, + setSort, + total, + }; +}; + +const fetchReferences = ({ + reference, + referenceId, + resource, + target, + filter, + source, + dispatch, + page, + perPage, + sort, +}) => () => { + const relatedTo = nameRelatedTo( + reference, + referenceId, + resource, + target, + filter + ); + + dispatch( + crudGetManyReference( + reference, + target, + referenceId, + relatedTo, + { page, perPage }, + sort, + filter, + source + ) + ); +}; + +const selectData = (reference, relatedTo) => state => + getReferences(state, reference, relatedTo); + +const selectIds = relatedTo => state => getIds(state, relatedTo); +const selectTotal = relatedTo => state => getTotal(state, relatedTo); + +export default useReferenceMany;