diff --git a/packages/ra-core/src/controller/field/ReferenceManyFieldController.spec.tsx b/packages/ra-core/src/controller/field/ReferenceManyFieldController.spec.tsx
index 4bd21889fa8..cc401f4839a 100644
--- a/packages/ra-core/src/controller/field/ReferenceManyFieldController.spec.tsx
+++ b/packages/ra-core/src/controller/field/ReferenceManyFieldController.spec.tsx
@@ -1,103 +1,157 @@
import React from 'react';
import assert from 'assert';
import { render } from 'react-testing-library';
-import { UnconnectedReferenceManyFieldController as ReferenceManyFieldController } from './ReferenceManyFieldController';
+
+import ReferenceManyFieldController from './ReferenceManyFieldController';
+import renderWithRedux from '../../util/renderWithRedux';
describe('', () => {
it('should set loadedOnce to false when related records are not yet fetched', () => {
- const children = jest.fn().mockReturnValue('children');;
- const crudGetManyReference = jest.fn();
- render(
+ const children = jest.fn().mockReturnValue('children');
+ const { dispatch } = renderWithRedux(
{children}
-
+ ,
+ {
+ admin: {
+ resources: {
+ bar: {
+ data: {
+ 1: { id: 1, title: 'hello' },
+ 2: { id: 2, title: 'world' },
+ },
+ },
+ },
+ references: {
+ oneToMany: {
+ 'foo_bar@fooId_barId': {
+ ids: [1, 2],
+ },
+ },
+ },
+ },
+ }
);
- assert.equal(children.mock.calls[0][0].loadedOnce, false);
+ assert.deepEqual(dispatch.mock.calls[0], [
+ {
+ meta: {
+ fetch: 'GET_MANY_REFERENCE',
+ onFailure: {
+ notification: {
+ body: 'ra.notification.http_error',
+ level: 'warning',
+ },
+ },
+ relatedTo: 'foo_bar@foo_id_undefined',
+ resource: 'bar',
+ },
+ payload: {
+ filter: {},
+ id: undefined,
+ pagination: { page: 1, perPage: 25 },
+ sort: { field: 'id', order: 'DESC' },
+ source: undefined,
+ target: 'foo_id',
+ },
+ type: 'RA/CRUD_GET_MANY_REFERENCE',
+ },
+ ]);
});
it('should pass data and ids to children function', () => {
- const children = jest.fn().mockReturnValue('children');;
- const crudGetManyReference = jest.fn();
+ const children = jest.fn().mockReturnValue('children');
const data = {
1: { id: 1, title: 'hello' },
2: { id: 2, title: 'world' },
};
- render(
+ renderWithRedux(
{children}
-
+ ,
+ {
+ admin: {
+ resources: {
+ bar: {
+ data: {
+ 1: { id: 1, title: 'hello' },
+ 2: { id: 2, title: 'world' },
+ },
+ },
+ },
+ references: {
+ oneToMany: {
+ 'foo_bar@fooId_barId': {
+ ids: [1, 2],
+ },
+ },
+ },
+ },
+ }
);
assert.deepEqual(children.mock.calls[0][0].data, data);
assert.deepEqual(children.mock.calls[0][0].ids, [1, 2]);
});
it('should support record with string identifier', () => {
- const children = jest.fn().mockReturnValue('children');;
- const crudGetManyReference = jest.fn();
- const data = {
- 'abc-1': { id: 'abc-1', title: 'hello' },
- 'abc-2': { id: 'abc-2', title: 'world' },
- };
- render(
+ const children = jest.fn().mockReturnValue('children');
+ renderWithRedux(
{children}
-
+ ,
+ {
+ admin: {
+ resources: {
+ bar: {
+ data: {
+ 'abc-1': { id: 'abc-1', title: 'hello' },
+ 'abc-2': { id: 'abc-2', title: 'world' },
+ },
+ },
+ },
+ references: {
+ oneToMany: {
+ 'foo_bar@fooId_barId': {
+ ids: ['abc-1', 'abc-2'],
+ },
+ },
+ },
+ },
+ }
);
- assert.deepEqual(children.mock.calls[0][0].data, data);
+ assert.deepEqual(children.mock.calls[0][0].data, {
+ 'abc-1': { id: 'abc-1', title: 'hello' },
+ 'abc-2': { id: 'abc-2', title: 'world' },
+ });
assert.deepEqual(children.mock.calls[0][0].ids, ['abc-1', 'abc-2']);
});
- it('should support record with number identifier', () => {
- const children = jest.fn().mockReturnValue('children');;
- const crudGetManyReference = jest.fn();
- const data = {
- 1: { id: 1, title: 'hello' },
- 2: { id: 2, title: 'world' },
- };
- render(
-
- {children}
-
- );
- assert.deepEqual(children.mock.calls[0][0].data, data);
- assert.deepEqual(children.mock.calls[0][0].ids, [1, 2]);
- });
-
it('should support custom source', () => {
const children = jest.fn().mockReturnValue('children');
- const crudGetManyReference = jest.fn();
- render(
+ const { dispatch } = renderWithRedux(
', () => {
basePath=""
record={{ id: 'not me', customId: 1 }}
source="customId"
- crudGetManyReference={crudGetManyReference}
>
{children}
);
- assert.equal(crudGetManyReference.mock.calls[0][2], 1);
+ assert.deepEqual(dispatch.mock.calls[0], [
+ {
+ meta: {
+ fetch: 'GET_MANY_REFERENCE',
+ onFailure: {
+ notification: {
+ body: 'ra.notification.http_error',
+ level: 'warning',
+ },
+ },
+ relatedTo: 'posts_comments@post_id_1',
+ resource: 'comments',
+ },
+ payload: {
+ filter: {},
+ id: 1,
+ pagination: { page: 1, perPage: 25 },
+ sort: { field: 'id', order: 'DESC' },
+ source: 'customId',
+ target: 'post_id',
+ },
+ type: 'RA/CRUD_GET_MANY_REFERENCE',
+ },
+ ]);
});
it('should call crudGetManyReference when its props changes', () => {
- const crudGetManyReference = jest.fn();
const ControllerWrapper = props => (
', () => {
reference="bar"
target="foo_id"
basePath=""
- data={{
- 1: { id: 1, title: 'hello' },
- 2: { id: 2, title: 'world' },
- }}
- ids={[1, 2]}
source="id"
- crudGetManyReference={crudGetManyReference}
{...props}
>
{() => 'null'}
);
- const { rerender } = render();
+ const { rerender, dispatch } = renderWithRedux();
rerender();
- expect(crudGetManyReference).toBeCalledTimes(2);
+ expect(dispatch).toBeCalledTimes(2);
- assert.deepEqual(crudGetManyReference.mock.calls[0], [
- 'bar',
- 'foo_id',
- 1,
- 'foo_bar@foo_id_1',
- { page: 1, perPage: 25 },
- { field: 'id', order: 'DESC' },
- {},
- 'id',
+ assert.deepEqual(dispatch.mock.calls[0], [
+ {
+ meta: {
+ fetch: 'GET_MANY_REFERENCE',
+ onFailure: {
+ notification: {
+ body: 'ra.notification.http_error',
+ level: 'warning',
+ },
+ },
+ relatedTo: 'foo_bar@foo_id_1',
+ resource: 'bar',
+ },
+ payload: {
+ filter: {},
+ id: 1,
+ pagination: { page: 1, perPage: 25 },
+ sort: { field: 'id', order: 'DESC' },
+ source: 'id',
+ target: 'foo_id',
+ },
+ type: 'RA/CRUD_GET_MANY_REFERENCE',
+ },
]);
- assert.deepEqual(crudGetManyReference.mock.calls[1], [
- 'bar',
- 'foo_id',
- 1,
- 'foo_bar@foo_id_1',
- { page: 1, perPage: 25 },
- { field: 'id', order: 'ASC' },
- {},
- 'id',
+ assert.deepEqual(dispatch.mock.calls[1], [
+ {
+ meta: {
+ fetch: 'GET_MANY_REFERENCE',
+ onFailure: {
+ notification: {
+ body: 'ra.notification.http_error',
+ level: 'warning',
+ },
+ },
+ relatedTo: 'foo_bar@foo_id_1',
+ resource: 'bar',
+ },
+ payload: {
+ filter: {},
+ id: 1,
+ pagination: { page: 1, perPage: 25 },
+ sort: { field: 'id', order: 'ASC' },
+ source: 'id',
+ target: 'foo_id',
+ },
+ type: 'RA/CRUD_GET_MANY_REFERENCE',
+ },
]);
});
});
diff --git a/packages/ra-core/src/controller/field/ReferenceManyFieldController.tsx b/packages/ra-core/src/controller/field/ReferenceManyFieldController.tsx
index 56a833e0884..4f70b49be02 100644
--- a/packages/ra-core/src/controller/field/ReferenceManyFieldController.tsx
+++ b/packages/ra-core/src/controller/field/ReferenceManyFieldController.tsx
@@ -1,8 +1,9 @@
-import { ReactNode, useState, useReducer, useEffect } from 'react';
-import { connect } from 'react-redux';
+import { ReactNode, useState, useReducer, useEffect, useMemo } from 'react';
+// @ts-ignore
+import { useSelector, useDispatch } from 'react-redux';
import get from 'lodash/get';
-import { crudGetManyReference as crudGetManyReferenceAction } from '../../actions';
+import { crudGetManyReference } from '../../actions';
import {
SORT_ASC,
SORT_DESC,
@@ -32,7 +33,6 @@ interface ChildrenFuncParams {
interface Props {
basePath: string;
children: (params: ChildrenFuncParams) => ReactNode;
- crudGetManyReference: Dispatch;
data?: RecordMap;
filter?: any;
ids?: any[];
@@ -106,29 +106,37 @@ const defaultFilter = {};
* ...
*
*/
-export const UnconnectedReferenceManyFieldController = ({
+export const ReferenceManyFieldController = ({
resource,
reference,
record,
target,
filter = defaultFilter,
source,
- crudGetManyReference,
- data,
- ids,
children,
basePath,
- total,
perPage = 25,
sort = { field: 'id', order: 'DESC' },
}) => {
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,
@@ -137,10 +145,10 @@ export const UnconnectedReferenceManyFieldController = ({
target,
filter,
source,
- crudGetManyReference,
page,
perPage: currentPerPage,
sort: currentSort,
+ dispatch,
}),
[
reference,
@@ -188,7 +196,7 @@ const fetchReferences = ({
target,
filter,
source,
- crudGetManyReference,
+ dispatch,
page,
perPage,
sort,
@@ -201,38 +209,24 @@ const fetchReferences = ({
filter
);
- crudGetManyReference(
- reference,
- target,
- referenceId,
- relatedTo,
- { page, perPage },
- sort,
- filter,
- source
+ dispatch(
+ crudGetManyReference(
+ reference,
+ target,
+ referenceId,
+ relatedTo,
+ { page, perPage },
+ sort,
+ filter,
+ source
+ )
);
};
-function mapStateToProps(state, props) {
- const relatedTo = nameRelatedTo(
- props.reference,
- get(props.record, props.source),
- props.resource,
- props.target,
- props.filter
- );
- return {
- data: getReferences(state, props.reference, relatedTo),
- ids: getIds(state, relatedTo),
- total: getTotal(state, relatedTo),
- };
-}
+const selectData = (reference, relatedTo) => state =>
+ getReferences(state, reference, relatedTo);
-const ReferenceManyFieldController = connect(
- mapStateToProps,
- {
- crudGetManyReference: crudGetManyReferenceAction,
- }
-)(UnconnectedReferenceManyFieldController);
+const selectIds = relatedTo => state => getIds(state, relatedTo);
+const selectTotal = relatedTo => state => getTotal(state, relatedTo);
export default ReferenceManyFieldController;