From bbf8b82d122e600a2c6074e245ba63317c3ea1a9 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Tue, 29 Oct 2019 11:04:23 +0100 Subject: [PATCH] Fix ReferenceArrayInput does not work in Filters closes #3883 --- .../input/useReferenceArrayInputController.ts | 24 +- .../ra-core/src/dataProvider/useGetMany.ts | 15 +- .../src/dataProvider/useGetMatching.ts | 13 +- .../src/input/ReferenceArrayInput.js | 248 +++++++++--------- .../src/input/SelectArrayInput.tsx | 3 +- 5 files changed, 165 insertions(+), 138 deletions(-) diff --git a/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts b/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts index 8aa027fa7f4..e103c0b1ea0 100644 --- a/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts +++ b/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts @@ -1,7 +1,7 @@ import { useMemo, useState, useEffect, useRef } from 'react'; import isEqual from 'lodash/isEqual'; import difference from 'lodash/difference'; -import { Record, Pagination, Sort } from '../../types'; +import { Pagination, Record, Sort } from '../../types'; import { useGetMany } from '../../dataProvider'; import { FieldInputProps } from 'react-final-form'; import useGetMatching from '../../dataProvider/useGetMatching'; @@ -13,7 +13,7 @@ import { getStatusForArrayInput as getDataStatus } from './referenceDataStatus'; * * @example * - * const { choices, error, loaded, loading, referenceBasePath } = useReferenceArrayInputController({ + * const { choices, error, loaded, loading } = useReferenceArrayInputController({ * basePath: 'resource'; * record: { referenceIds: ['id1', 'id2']}; * reference: 'reference'; @@ -33,7 +33,6 @@ import { getStatusForArrayInput as getDataStatus } from './referenceDataStatus'; * @return {Object} controllerProps Fetched data and callbacks for the ReferenceArrayInput components */ const useReferenceArrayInputController = ({ - basePath, filter: defaultFilter, filterToQuery = defaultFilterToQuery, input, @@ -115,7 +114,7 @@ const useReferenceArrayInputController = ({ // the component displaying the currently selected records may fail const finalMatchingReferences = matchingReferences && matchingReferences.length > 0 - ? matchingReferences.concat(finalReferenceRecords) + ? mergeReferences(matchingReferences, finalReferenceRecords) : finalReferenceRecords.length > 0 ? finalReferenceRecords : matchingReferences; @@ -127,13 +126,11 @@ const useReferenceArrayInputController = ({ translate, }); - const referenceBasePath = basePath.replace(resource, reference); // FIXME obviously very weak return { choices: dataStatus.choices, error: dataStatus.error, loaded, loading: dataStatus.waiting, - referenceBasePath, setFilter, setPagination, setSort, @@ -141,6 +138,19 @@ const useReferenceArrayInputController = ({ }; }; +// concatenate and deduplicate two lists of records +const mergeReferences = (ref1: Record[], ref2: Record[]): Record[] => { + const res = [...ref1]; + const ids = ref1.map(ref => ref.id); + ref2.forEach(ref => { + if (!ids.includes(ref.id)) { + ids.push(ref.id); + res.push(ref); + } + }); + return res; +}; + export default useReferenceArrayInputController; /** @@ -151,7 +161,6 @@ export default useReferenceArrayInputController; * @property {Object} error the error returned by the dataProvider * @property {boolean} loading is the reference currently loading * @property {boolean} loaded has the reference already been loaded - * @property {string} referenceBasePath basePath of the reference */ interface ReferenceArrayInputProps { choices: Record[]; @@ -159,7 +168,6 @@ interface ReferenceArrayInputProps { warning?: any; loading: boolean; loaded: boolean; - referenceBasePath: string; setFilter: (filter: any) => void; setPagination: (pagination: Pagination) => void; setSort: (sort: Sort) => void; diff --git a/packages/ra-core/src/dataProvider/useGetMany.ts b/packages/ra-core/src/dataProvider/useGetMany.ts index e87e8f9f554..45a81d4f1d8 100644 --- a/packages/ra-core/src/dataProvider/useGetMany.ts +++ b/packages/ra-core/src/dataProvider/useGetMany.ts @@ -7,7 +7,7 @@ import union from 'lodash/union'; import isEqual from 'lodash/isEqual'; import { CRUD_GET_MANY } from '../actions/dataActions/crudGetMany'; -import { Identifier, ReduxState, DataProviderProxy } from '../types'; +import { Identifier, Record, ReduxState, DataProviderProxy } from '../types'; import { useSafeSetState } from '../util/hooks'; import useDataProvider from './useDataProvider'; import { useEffect } from 'react'; @@ -23,7 +23,12 @@ interface Query { interface QueriesToCall { [resource: string]: Query[]; } - +interface UseGetManyResult { + data: Record[]; + error?: any; + loading: boolean; + loaded: boolean; +} let queriesToCall: QueriesToCall = {}; let dataProvider: DataProviderProxy; @@ -74,7 +79,11 @@ const DataProviderOptions = { action: CRUD_GET_MANY }; * ); * }; */ -const useGetMany = (resource: string, ids: Identifier[], options: any = {}) => { +const useGetMany = ( + resource: string, + ids: Identifier[], + options: any = {} +): UseGetManyResult => { // we can't use useQueryWithStore here because we're aggregating queries first // therefore part of the useQueryWithStore logic will have to be repeated below const selectMany = useMemo(makeGetManySelector, []); diff --git a/packages/ra-core/src/dataProvider/useGetMatching.ts b/packages/ra-core/src/dataProvider/useGetMatching.ts index c104caa3e1b..e87d5a027e7 100644 --- a/packages/ra-core/src/dataProvider/useGetMatching.ts +++ b/packages/ra-core/src/dataProvider/useGetMatching.ts @@ -1,6 +1,6 @@ import { useSelector } from 'react-redux'; import { CRUD_GET_MATCHING } from '../actions/dataActions/crudGetMatching'; -import { Pagination, Sort, ReduxState } from '../types'; +import { Identifier, Pagination, Sort, Record, ReduxState } from '../types'; import useQueryWithStore from './useQueryWithStore'; import { getReferenceResource, @@ -66,7 +66,7 @@ const useGetMatching = ( source: string, referencingResource: string, options?: any -) => { +): UseGetMatchingResult => { const relatedTo = referenceSource(referencingResource, source); const { data: possibleValues, @@ -119,4 +119,13 @@ const useGetMatching = ( }; }; +interface UseGetMatchingResult { + data: Record[]; + ids: Identifier[]; + total: number; + error?: any; + loading: boolean; + loaded: boolean; +} + export default useGetMatching; diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.js b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.js index ca1501cc9d7..7e79bb67374 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.js +++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.js @@ -10,130 +10,6 @@ import LinearProgress from '../layout/LinearProgress'; import Labeled from '../input/Labeled'; import ReferenceError from './ReferenceError'; -const sanitizeRestProps = ({ - alwaysOn, - basePath, - component, - crudGetMany, - crudGetMatching, - defaultValue, - filterToQuery, - formClassName, - initializeForm, - input, - isRequired, - label, - locale, - meta, - optionText, - optionValue, - perPage, - record, - referenceSource, - resource, - allowEmpty, - source, - textAlign, - translate, - translateChoice, - ...rest -}) => rest; - -export const ReferenceArrayInputView = ({ - allowEmpty, - basePath, - children, - choices, - className, - error, - input, - loading, - isRequired, - label, - meta, - onChange, - options, - resource, - setFilter, - setPagination, - setSort, - source, - translate, - warning, - ...rest -}) => { - const translatedLabel = translate( - label || `resources.${resource}.fields.${source}`, - { _: label } - ); - - if (loading) { - return ( - - - - ); - } - - if (error) { - return ; - } - - return React.cloneElement(children, { - allowEmpty, - basePath, - choices, - className, - error, - input, - isRequired, - label: translatedLabel, - meta: { - ...meta, - helperText: warning || false, - }, - onChange, - options, - resource, - setFilter, - setPagination, - setSort, - source, - translateChoice: false, - limitChoicesToValue: true, - ...sanitizeRestProps(rest), - ...children.props, - }); -}; - -ReferenceArrayInputView.propTypes = { - allowEmpty: PropTypes.bool, - basePath: PropTypes.string, - children: PropTypes.element, - choices: PropTypes.array, - className: PropTypes.string, - error: PropTypes.string, - loading: PropTypes.bool, - input: PropTypes.object.isRequired, - label: PropTypes.string, - meta: PropTypes.object, - onChange: PropTypes.func, - options: PropTypes.object, - resource: PropTypes.string.isRequired, - setFilter: PropTypes.func, - setPagination: PropTypes.func, - setSort: PropTypes.func, - source: PropTypes.string, - translate: PropTypes.func.isRequired, - warning: PropTypes.string, -}; - /** * An Input component for fields containing a list of references to another resource. * Useful for 'hasMany' relationship. @@ -283,4 +159,128 @@ ReferenceArrayInput.defaultProps = { sort: { field: 'id', order: 'DESC' }, }; +const sanitizeRestProps = ({ + alwaysOn, + basePath, + component, + crudGetMany, + crudGetMatching, + defaultValue, + filterToQuery, + formClassName, + initializeForm, + input, + isRequired, + label, + locale, + meta, + optionText, + optionValue, + perPage, + record, + referenceSource, + resource, + allowEmpty, + source, + textAlign, + translate, + translateChoice, + ...rest +}) => rest; + +export const ReferenceArrayInputView = ({ + allowEmpty, + basePath, + children, + choices, + className, + error, + input, + loading, + isRequired, + label, + meta, + onChange, + options, + resource, + setFilter, + setPagination, + setSort, + source, + translate, + warning, + ...rest +}) => { + const translatedLabel = translate( + label || `resources.${resource}.fields.${source}`, + { _: label } + ); + + if (loading) { + return ( + + + + ); + } + + if (error) { + return ; + } + + return React.cloneElement(children, { + allowEmpty, + basePath, + choices, + className, + error, + input, + isRequired, + label: translatedLabel, + meta: { + ...meta, + helperText: warning || false, + }, + onChange, + options, + resource, + setFilter, + setPagination, + setSort, + source, + translateChoice: false, + limitChoicesToValue: true, + ...sanitizeRestProps(rest), + ...children.props, + }); +}; + +ReferenceArrayInputView.propTypes = { + allowEmpty: PropTypes.bool, + basePath: PropTypes.string, + children: PropTypes.element, + choices: PropTypes.array, + className: PropTypes.string, + error: PropTypes.string, + loading: PropTypes.bool, + input: PropTypes.object.isRequired, + label: PropTypes.string, + meta: PropTypes.object, + onChange: PropTypes.func, + options: PropTypes.object, + resource: PropTypes.string.isRequired, + setFilter: PropTypes.func, + setPagination: PropTypes.func, + setSort: PropTypes.func, + source: PropTypes.string, + translate: PropTypes.func.isRequired, + warning: PropTypes.string, +}; + export default ReferenceArrayInput; diff --git a/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx b/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx index 30e9cace75f..5b90afa68a5 100644 --- a/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx +++ b/packages/ra-ui-materialui/src/input/SelectArrayInput.tsx @@ -41,6 +41,7 @@ const sanitizeRestProps = ({ isRequired, label, limitChoicesToValue, + loaded, locale, meta, onChange, @@ -262,7 +263,7 @@ const SelectArrayInput: FunctionComponent< }; SelectArrayInput.propTypes = { - choices: PropTypes.arrayOf(PropTypes.object).isRequired, + choices: PropTypes.arrayOf(PropTypes.object), classes: PropTypes.object, className: PropTypes.string, children: PropTypes.node,