diff --git a/packages/ra-core/src/controller/input/ReferenceArrayInputController.spec.tsx b/packages/ra-core/src/controller/input/ReferenceArrayInputController.spec.tsx index 6440ac76a51..e40c3e5ac73 100644 --- a/packages/ra-core/src/controller/input/ReferenceArrayInputController.spec.tsx +++ b/packages/ra-core/src/controller/input/ReferenceArrayInputController.spec.tsx @@ -564,7 +564,14 @@ describe('', () => { input={{ value: [5] }} > {children} - + , + { + admin: { + resources: { tags: { data: {}, list: {} } }, + references: { possibleValues: {} }, + ui: { viewVersion: 1 }, + }, + } ); await wait(); diff --git a/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts b/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts index e103c0b1ea0..0ff5e2707fd 100644 --- a/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts +++ b/packages/ra-core/src/controller/input/useReferenceArrayInputController.ts @@ -1,7 +1,8 @@ import { useMemo, useState, useEffect, useRef } from 'react'; +import { useSelector } from 'react-redux'; import isEqual from 'lodash/isEqual'; import difference from 'lodash/difference'; -import { Pagination, Record, Sort } from '../../types'; +import { Pagination, Record, Sort, ReduxState } from '../../types'; import { useGetMany } from '../../dataProvider'; import { FieldInputProps } from 'react-final-form'; import useGetMatching from '../../dataProvider/useGetMatching'; @@ -49,12 +50,18 @@ const useReferenceArrayInputController = ({ // only the missing references when the input value changes const inputValue = useRef(input.value); const [idsToFetch, setIdsToFetch] = useState(input.value); + const [idsToGetFromStore, setIdsToGetFromStore] = useState([]); + const referenceRecordsFromStore = useSelector((state: ReduxState) => + idsToGetFromStore.map(id => state.admin.resources[reference].data[id]) + ); + // optimization: we fetch selected items only once. When the user selects more items, + // as we already have the past selected items in the store, we don't fetch them. useEffect(() => { const newIdsToFetch = difference(input.value, inputValue.current); - if (newIdsToFetch.length > 0) { setIdsToFetch(newIdsToFetch); + setIdsToGetFromStore(inputValue.current); } inputValue.current = input.value; }, [input.value, setIdsToFetch]); @@ -90,11 +97,18 @@ const useReferenceArrayInputController = ({ [defaultFilter, filter, filterToQuery] ); - const { data: referenceRecords, loaded } = useGetMany( + const { data: referenceRecordsFetched, loaded } = useGetMany( reference, idsToFetch || [] ); + const referenceRecords = referenceRecordsFetched + ? referenceRecordsFetched.concat(referenceRecordsFromStore) + : referenceRecordsFromStore; + + // filter out not found references - happens when the dataProvider doesn't guarantee referential integrity + const finalReferenceRecords = referenceRecords.filter(Boolean); + const { data: matchingReferences } = useGetMatching( reference, pagination, @@ -105,11 +119,6 @@ const useReferenceArrayInputController = ({ options ); - // filter out not found references - happens when the dataProvider doesn't guarantee referential integrity - const finalReferenceRecords = referenceRecords - ? referenceRecords.filter(Boolean) - : []; - // We merge the currently selected records with the matching ones, otherwise // the component displaying the currently selected records may fail const finalMatchingReferences = diff --git a/packages/ra-core/src/dataProvider/defaultDataProvider.ts b/packages/ra-core/src/dataProvider/defaultDataProvider.ts index ed0e4294c96..8455e83d442 100644 --- a/packages/ra-core/src/dataProvider/defaultDataProvider.ts +++ b/packages/ra-core/src/dataProvider/defaultDataProvider.ts @@ -1,11 +1,11 @@ export default { - create: () => Promise.resolve(null), // avoids adding a context in tests - delete: () => Promise.resolve(null), // avoids adding a context in tests - deleteMany: () => Promise.resolve(null), // avoids adding a context in tests - getList: () => Promise.resolve(null), // avoids adding a context in tests - getMany: () => Promise.resolve(null), // avoids adding a context in tests - getManyReference: () => Promise.resolve(null), // avoids adding a context in tests - getOne: () => Promise.resolve(null), // avoids adding a context in tests - update: () => Promise.resolve(null), // avoids adding a context in tests - updateMany: () => Promise.resolve(null), // avoids adding a context in tests + create: () => Promise.resolve({ data: null }), // avoids adding a context in tests + delete: () => Promise.resolve({ data: null }), // avoids adding a context in tests + deleteMany: () => Promise.resolve({ data: [] }), // avoids adding a context in tests + getList: () => Promise.resolve({ data: [], total: 0 }), // avoids adding a context in tests + getMany: () => Promise.resolve({ data: [] }), // avoids adding a context in tests + getManyReference: () => Promise.resolve({ data: [], total: 0 }), // avoids adding a context in tests + getOne: () => Promise.resolve({ data: null }), // avoids adding a context in tests + update: () => Promise.resolve({ data: null }), // avoids adding a context in tests + updateMany: () => Promise.resolve({ data: [] }), // avoids adding a context in tests };