diff --git a/docs/Resource.md b/docs/Resource.md
index 7ceeff9b047..1ed0cc7bc25 100644
--- a/docs/Resource.md
+++ b/docs/Resource.md
@@ -145,6 +145,40 @@ For instance, to change the default representation of "users" records to render
- a function (e.g. `(record) => record.title`) to specify a custom string representation
- a React component (e.g. ``). In such components, use [`useRecordContext`](./useRecordContext.md) to access the record.
+## `hasCreate`, `hasEdit`, `hasShow`
+
+Some components, like [``](./CreateDialog.md), [``](./EditDialog.md) or [``](./ShowDialog.md) need to declare the CRUD components outside of the `` component. In such cases, you can use the `hasCreate`, `hasEdit` and `hasShow` props to tell react-admin which CRUD components are available for a given resource.
+
+This is useful, for instance, to have the `` component display a link to the edit or show view of the referenced record.
+
+```jsx
+// in src/App.js
+import { Admin, Resource } from 'react-admin';
+import { dataProvider } from './dataProvider';
+
+import { PostList } from './posts';
+import { CommentEdit } from './commentEdit';
+
+const App = () => (
+
+
+
+
+);
+
+// in src/commentEdit.js
+import { Edit, SimpleForm, ReferenceField } from 'react-admin';
+
+const CommentEdit = () => (
+
+
+ {/* renders a link to the edit view only because `hasEdit` has been set on `` */}
+
+
+
+);
+```
+
## Resource Context
`` also creates a `ResourceContext`, that gives access to the current resource name to all descendants of the main page components (`list`, `create`, `edit`, `show`).
diff --git a/packages/ra-core/src/core/Resource.tsx b/packages/ra-core/src/core/Resource.tsx
index 8ba5ea27a71..3aae32cb33e 100644
--- a/packages/ra-core/src/core/Resource.tsx
+++ b/packages/ra-core/src/core/Resource.tsx
@@ -52,13 +52,16 @@ Resource.registerResource = ({
options,
show,
recordRepresentation,
+ hasCreate,
+ hasEdit,
+ hasShow,
}: ResourceProps) => ({
name,
options,
hasList: !!list,
- hasCreate: !!create,
- hasEdit: !!edit,
- hasShow: !!show,
+ hasCreate: !!create || !!hasCreate,
+ hasEdit: !!edit || !!hasEdit,
+ hasShow: !!show || !!hasShow,
icon,
recordRepresentation,
});
diff --git a/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.spec.tsx b/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.spec.tsx
index 61e2572f9df..c044598d58f 100644
--- a/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.spec.tsx
+++ b/packages/ra-core/src/core/useConfigureAdminRouterFromChildren.spec.tsx
@@ -219,6 +219,30 @@ const TestedComponentWithOnlyLazyCustomRoutes = ({ history }) => {
);
};
+const TestedComponentWithForcedRoutes = () => {
+ const history = createMemoryHistory();
+
+ return (
+
+
+ }
+ hasCreate
+ hasEdit
+ hasShow
+ />
+ } />
+ {() => [} hasEdit />]}
+
+
+ );
+};
+
const expectResource = (resource: string) =>
expect(screen.queryByText(`"name":"${resource}"`, { exact: false }));
@@ -324,4 +348,21 @@ describe('useConfigureAdminRouterFromChildren', () => {
history.push('/foo');
expect(screen.queryByText('Foo')).not.toBeNull();
});
+ it('should support forcing hasEdit hasCreate or hasShow', async () => {
+ render();
+ await waitFor(() => expect(screen.queryByText('Loading')).toBeNull());
+
+ expectResourceView('posts', 'list').not.toBeNull();
+ expectResourceView('posts', 'create').not.toBeNull();
+ expectResourceView('posts', 'edit').not.toBeNull();
+ expectResourceView('posts', 'show').not.toBeNull();
+ expectResourceView('comments', 'list').not.toBeNull();
+ expectResourceView('comments', 'create').toBeNull();
+ expectResourceView('comments', 'edit').toBeNull();
+ expectResourceView('comments', 'show').toBeNull();
+ expectResourceView('user', 'list').not.toBeNull();
+ expectResourceView('user', 'create').toBeNull();
+ expectResourceView('user', 'edit').not.toBeNull();
+ expectResourceView('user', 'show').toBeNull();
+ });
});
diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts
index 53ac89c2160..be2bcbe9dae 100644
--- a/packages/ra-core/src/types.ts
+++ b/packages/ra-core/src/types.ts
@@ -346,6 +346,9 @@ export interface ResourceProps {
create?: ComponentType | ReactElement;
edit?: ComponentType | ReactElement;
show?: ComponentType | ReactElement;
+ hasCreate?: boolean;
+ hasEdit?: boolean;
+ hasShow?: boolean;
icon?: ComponentType;
recordRepresentation?: ReactElement | RecordToStringFunction | string;
options?: ResourceOptions;