diff --git a/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx
index 6e202918a10..78d96edb9d5 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceArrayField.spec.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import expect from 'expect';
-import { render, act } from '@testing-library/react';
+import { render, act, waitFor } from '@testing-library/react';
import { renderWithRedux } from 'ra-test';
import { MemoryRouter } from 'react-router-dom';
import { ListContextProvider, DataProviderContext } from 'ra-core';
@@ -244,4 +244,64 @@ describe('', () => {
await new Promise(resolve => setTimeout(resolve)); // wait for loaded to be true
expect(queryByText('bar1')).not.toBeNull();
});
+
+ it('should throw an error if used without a Resource for the reference', async () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ class ErrorBoundary extends React.Component<
+ {
+ onError?: (
+ error: Error,
+ info: { componentStack: string }
+ ) => void;
+ },
+ { error: Error | null }
+ > {
+ constructor(props) {
+ super(props);
+ this.state = { error: null };
+ }
+
+ static getDerivedStateFromError(error) {
+ // Update state so the next render will show the fallback UI.
+ return { error };
+ }
+
+ componentDidCatch(error, errorInfo) {
+ // You can also log the error to an error reporting service
+ this.props.onError(error, errorInfo);
+ }
+
+ render() {
+ if (this.state.error) {
+ // You can render any custom fallback UI
+ return
Something went wrong.
;
+ }
+
+ return this.props.children;
+ }
+ }
+ const onError = jest.fn();
+ renderWithRedux(
+
+
+
+
+
+
+ ,
+ { admin: { resources: { comments: { data: {} } } } }
+ );
+ await waitFor(() => {
+ expect(onError.mock.calls[0][0].message).toBe(
+ 'You must declare a in order to use a '
+ );
+ });
+ });
});
diff --git a/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx b/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx
index 31f913d4a96..a733dd7827f 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceArrayField.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import { Children, cloneElement, FC, memo, ReactElement } from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
+import { useSelector } from 'react-redux';
import {
ListContextProvider,
useListContext,
@@ -11,6 +12,7 @@ import {
FilterPayload,
ResourceContextProvider,
useRecordContext,
+ ReduxState,
} from 'ra-core';
import { fieldPropTypes, PublicFieldProps, InjectedFieldProps } from './types';
@@ -93,6 +95,17 @@ const ReferenceArrayField: FC = props => {
' only accepts a single child (like )'
);
}
+
+ const isReferenceDeclared = useSelector(
+ state => typeof state.admin.resources[props.reference] !== 'undefined'
+ );
+
+ if (!isReferenceDeclared) {
+ throw new Error(
+ `You must declare a in order to use a `
+ );
+ }
+
const controllerProps = useReferenceArrayFieldController({
basePath,
filter,
diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx
index b522b9de872..9ff80cd2efa 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx
@@ -131,7 +131,8 @@ describe('', () => {
>
-
+ ,
+ { admin: { resources: { posts: { data: {} } } } }
);
await new Promise(resolve => setTimeout(resolve, 10));
expect(queryByRole('progressbar')).toBeNull();
@@ -156,7 +157,8 @@ describe('', () => {
>
-
+ ,
+ { admin: { resources: { posts: { data: {} } } } }
);
await new Promise(resolve => setTimeout(resolve, 10));
expect(queryByRole('progressbar')).toBeNull();
@@ -176,7 +178,8 @@ describe('', () => {
emptyText="EMPTY"
>
-
+ ,
+ { admin: { resources: { posts: { data: {} } } } }
);
expect(getByText('EMPTY')).not.toBeNull();
});
@@ -260,7 +263,8 @@ describe('', () => {
-
+ ,
+ { admin: { resources: { posts: { data: {} } } } }
);
await waitFor(() => {
const action = dispatch.mock.calls[0][0];
@@ -286,7 +290,8 @@ describe('', () => {
>
-
+ ,
+ { admin: { resources: { posts: { data: {} } } } }
);
await waitFor(() => {
const ErrorIcon = getByRole('presentation', { hidden: true });
@@ -295,6 +300,63 @@ describe('', () => {
});
});
+ it('should throw an error if used without a Resource for the reference', async () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ class ErrorBoundary extends React.Component<
+ {
+ onError?: (
+ error: Error,
+ info: { componentStack: string }
+ ) => void;
+ },
+ { error: Error | null }
+ > {
+ constructor(props) {
+ super(props);
+ this.state = { error: null };
+ }
+
+ static getDerivedStateFromError(error) {
+ // Update state so the next render will show the fallback UI.
+ return { error };
+ }
+
+ componentDidCatch(error, errorInfo) {
+ // You can also log the error to an error reporting service
+ this.props.onError(error, errorInfo);
+ }
+
+ render() {
+ if (this.state.error) {
+ // You can render any custom fallback UI
+ return Something went wrong.
;
+ }
+
+ return this.props.children;
+ }
+ }
+ const onError = jest.fn();
+ renderWithRedux(
+
+
+
+
+ ,
+ { admin: { resources: { comments: { data: {} } } } }
+ );
+ await waitFor(() => {
+ expect(onError.mock.calls[0][0].message).toBe(
+ 'You must declare a in order to use a '
+ );
+ });
+ });
+
describe('ReferenceFieldView', () => {
it('should render a link to specified resourceLinkPath', () => {
const { container } = render(
diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.tsx b/packages/ra-ui-materialui/src/field/ReferenceField.tsx
index fb98669b97b..db5625cdf4d 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceField.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceField.tsx
@@ -6,6 +6,7 @@ import get from 'lodash/get';
import { makeStyles } from '@material-ui/core/styles';
import { Typography } from '@material-ui/core';
import ErrorIcon from '@material-ui/icons/Error';
+import { useSelector } from 'react-redux';
import {
useReference,
UseReferenceProps,
@@ -15,6 +16,7 @@ import {
RecordContextProvider,
Record,
useRecordContext,
+ ReduxState,
} from 'ra-core';
import LinearProgress from '../layout/LinearProgress';
@@ -70,6 +72,16 @@ import { ClassesOverride } from '../types';
const ReferenceField: FC = props => {
const { source, emptyText, ...rest } = props;
const record = useRecordContext(props);
+ const isReferenceDeclared = useSelector(
+ state => typeof state.admin.resources[props.reference] !== 'undefined'
+ );
+
+ if (!isReferenceDeclared) {
+ throw new Error(
+ `You must declare a in order to use a `
+ );
+ }
+
return get(record, source) == null ? (
emptyText ? (
diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx
index dad58468ed4..cbf318132f6 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx
@@ -1,8 +1,13 @@
import * as React from 'react';
-import { render } from '@testing-library/react';
+import expect from 'expect';
+import { render, waitFor } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
-import { ReferenceManyFieldView } from './ReferenceManyField';
+import { renderWithRedux } from 'ra-test';
+
+import ReferenceManyField, {
+ ReferenceManyFieldView,
+} from './ReferenceManyField';
import TextField from './TextField';
import SingleFieldList from '../list/SingleFieldList';
@@ -112,4 +117,63 @@ describe('', () => {
expect(links[0].getAttribute('href')).toEqual('/posts/1');
expect(links[1].getAttribute('href')).toEqual('/posts/2');
});
+
+ it('should throw an error if used without a Resource for the reference', async () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+ class ErrorBoundary extends React.Component<
+ {
+ onError?: (
+ error: Error,
+ info: { componentStack: string }
+ ) => void;
+ },
+ { error: Error | null }
+ > {
+ constructor(props) {
+ super(props);
+ this.state = { error: null };
+ }
+
+ static getDerivedStateFromError(error) {
+ // Update state so the next render will show the fallback UI.
+ return { error };
+ }
+
+ componentDidCatch(error, errorInfo) {
+ // You can also log the error to an error reporting service
+ this.props.onError(error, errorInfo);
+ }
+
+ render() {
+ if (this.state.error) {
+ // You can render any custom fallback UI
+ return Something went wrong.
;
+ }
+
+ return this.props.children;
+ }
+ }
+ const onError = jest.fn();
+ renderWithRedux(
+
+
+
+
+
+
+ ,
+ { admin: { resources: { comments: { data: {} } } } }
+ );
+ await waitFor(() => {
+ expect(onError.mock.calls[0][0].message).toBe(
+ 'You must declare a in order to use a '
+ );
+ });
+ });
});
diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx
index 3fa136b0613..e5ddda8b55b 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx
+++ b/packages/ra-ui-materialui/src/field/ReferenceManyField.tsx
@@ -8,7 +8,9 @@ import {
ListControllerProps,
ResourceContextProvider,
useRecordContext,
+ ReduxState,
} from 'ra-core';
+import { useSelector } from 'react-redux';
import { PublicFieldProps, fieldPropTypes, InjectedFieldProps } from './types';
import sanitizeFieldRestProps from './sanitizeFieldRestProps';
@@ -80,6 +82,16 @@ export const ReferenceManyField: FC = props => {
);
}
+ const isReferenceDeclared = useSelector(
+ state => typeof state.admin.resources[props.reference] !== 'undefined'
+ );
+
+ if (!isReferenceDeclared) {
+ throw new Error(
+ `You must declare a in order to use a `
+ );
+ }
+
const controllerProps = useReferenceManyFieldController({
basePath,
filter,