diff --git a/UPGRADE.md b/UPGRADE.md
index a384d2721b9..d22d3f66fc8 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -78,6 +78,16 @@ If you're using a Custom App, you had to render Resource components with the reg
+
```
+## ReferenceField prop `linkType` renamed to `link`
+
+When using the ReferenceField component, you should rename your `linkType` props to `link`. This prop now also accepts custom functions to return a link, see the Fields documentation.
+
+```diff
+-
++
+```
+
+
## `withDataProvider` no longer injects `dispatch`
The `withDataProvider` HOC used to inject two props: `dataProvider`, and redux' `dispatch`. This last prop is now easy to get via the `useDispatch` hook from Redux, so `withDataProvider` no longer injects it.
diff --git a/docs/Fields.md b/docs/Fields.md
index ceb00031900..c92aa1a5a23 100644
--- a/docs/Fields.md
+++ b/docs/Fields.md
@@ -495,10 +495,10 @@ With this configuration, `` wraps the user's name in a link to t
```
-To change the link from the `` page to the `` page, set the `linkType` prop to "show".
+To change the link from the `` page to the `` page, set the `link` prop to "show".
```jsx
-
+
```
@@ -511,11 +511,20 @@ By default, `` is sorted by its `source`. To specify another att
```
-You can also prevent `` from adding link to children by setting `linkType` to `false`.
+You can also prevent `` from adding link to children by setting `link` to `false`.
```jsx
// No link
-
+
+
+
+```
+
+You can also use a custom `link` function to get a custom path for the children. This function must accept `record` and `reference` as arguments.
+
+```jsx
+// Custom path
+ `/my/path/to/${reference}/${record.id}`}>
```
diff --git a/packages/ra-core/src/controller/field/ReferenceFieldController.spec.tsx b/packages/ra-core/src/controller/field/ReferenceFieldController.spec.tsx
index 7a3acc8caad..874b8d75df3 100644
--- a/packages/ra-core/src/controller/field/ReferenceFieldController.spec.tsx
+++ b/packages/ra-core/src/controller/field/ReferenceFieldController.spec.tsx
@@ -156,7 +156,7 @@ describe('', () => {
});
});
- it('should render a link to the Show page of the related record when the linkType is show', () => {
+ it('should render a link to the Show page of the related record when the link is show', () => {
const children = jest.fn().mockReturnValue(children);
renderWithRedux(
', () => {
resource="comments"
reference="posts"
basePath="/comments"
- linkType="show"
+ link="show"
>
{children}
,
@@ -179,7 +179,7 @@ describe('', () => {
});
});
- it('should accept edit as resource name when linkType is show', () => {
+ it('should accept edit as resource name when link is show', () => {
const children = jest.fn().mockReturnValue(children);
renderWithRedux(
', () => {
reference="edit"
resource="show"
basePath="/show"
- linkType="show"
+ link="show"
>
{children}
,
@@ -210,7 +210,7 @@ describe('', () => {
});
});
- it('should accept show as resource name when linkType is show', () => {
+ it('should accept show as resource name when link is show', () => {
const children = jest.fn().mockReturnValue(children);
renderWithRedux(
', () => {
resource="edit"
basePath="/edit"
crudGetManyAccumulate={crudGetManyAccumulate}
- linkType="show"
+ link="show"
>
{children}
,
@@ -242,7 +242,7 @@ describe('', () => {
});
});
- it('should set resourceLinkPath to false when the linkType is false', () => {
+ it('should set resourceLinkPath to false when the link is false', () => {
const children = jest.fn().mockReturnValue(children);
renderWithRedux(
', () => {
source="postId"
reference="posts"
basePath="/foo"
- linkType={false}
+ link={false}
>
{children}
,
diff --git a/packages/ra-core/src/controller/field/ReferenceFieldController.tsx b/packages/ra-core/src/controller/field/ReferenceFieldController.tsx
index be27eeb55b9..e8c7d7fb5c6 100644
--- a/packages/ra-core/src/controller/field/ReferenceFieldController.tsx
+++ b/packages/ra-core/src/controller/field/ReferenceFieldController.tsx
@@ -1,6 +1,6 @@
import { FunctionComponent, ReactNode, ReactElement } from 'react';
import { Record } from '../../types';
-import useReference, { UseReferenceProps } from './useReference';
+import useReference, { UseReferenceProps, LinkToFunctionType } from './useReference';
interface Props {
@@ -11,7 +11,7 @@ interface Props {
reference: string;
resource: string;
source: string;
- linkType: string | boolean;
+ link: string | boolean | LinkToFunctionType;
}
/**
@@ -28,18 +28,18 @@ interface Props {
* By default, includes a link to the page of the related record
* (`/users/:userId` in the previous example).
*
- * Set the linkType prop to "show" to link to the page instead.
+ * Set the link prop to "show" to link to the page instead.
*
* @example
- *
+ *
*
*
*
* You can also prevent `` from adding link to children by setting
- * `linkType` to false.
+ * `link` to false.
*
* @example
- *
+ *
*
*
*/
diff --git a/packages/ra-core/src/controller/field/useReference.ts b/packages/ra-core/src/controller/field/useReference.ts
index baa3b3329f5..c57edad72cf 100644
--- a/packages/ra-core/src/controller/field/useReference.ts
+++ b/packages/ra-core/src/controller/field/useReference.ts
@@ -7,6 +7,10 @@ import { crudGetManyAccumulate } from '../../actions';
import { linkToRecord } from '../../util';
import { Record, ReduxState } from '../../types';
+export type LinkToFunctionType = (record: Record, reference: string) => string;
+
+type LinkToType = string | boolean | LinkToFunctionType;
+
interface Option {
allowEmpty?: boolean;
basePath: string;
@@ -14,7 +18,8 @@ interface Option {
reference: string;
resource: string;
source: string;
- linkType: string | boolean;
+ link: LinkToType;
+ linkType?: LinkToType; // deprecated, use link instead
}
export interface UseReferenceProps {
@@ -28,7 +33,7 @@ export interface UseReferenceProps {
* @type {Object}
* @property {boolean} isLoading: boolean indicating if the reference has loaded
* @property {Object} referenceRecord: the referenced record.
- * @property {string | false} resourceLinkPath link to the page of the related record (depends on linkType) (false is no link)
+ * @property {string | false} resourceLinkPath link to the page of the related record (depends on link) (false is no link)
*/
/**
@@ -50,7 +55,8 @@ export interface UseReferenceProps {
* @param {Object} option
* @param {boolean} option.allowEmpty do we allow for no referenced record (default to false)
* @param {string} option.basePath basepath to current resource
- * @param {string | false} option.linkType The type of the link toward the referenced record. edit, show of false for no link (default to edit)
+ * @param {string | false | LinkToFunctionType} option.link="edit" The link toward the referenced record. 'edit', 'show' or false for no link (default to edit). Alternatively a function that returns a string
+ * @param {string | false | LinkToFunctionType} [option.linkType] DEPRECATED : old name for link
* @param {Object} option.record The The current resource record
* @param {string} option.reference The linked resource name
* @param {string} option.resource The current resource name
@@ -61,7 +67,8 @@ export interface UseReferenceProps {
export const useReference = ({
allowEmpty = false,
basePath,
- linkType = 'edit',
+ link = 'edit',
+ linkType,
record = { id: '' },
reference,
resource,
@@ -78,9 +85,23 @@ export const useReference = ({
}
}, [sourceId, reference]);
const rootPath = basePath.replace(resource, reference);
- const resourceLinkPath = !linkType
- ? false
- : linkToRecord(rootPath, sourceId, linkType as string);
+ // Backward compatibility: keep linkType but with warning
+ const getResourceLinkPath = (linkTo: LinkToType) =>
+ !linkTo
+ ? false
+ : typeof linkTo === 'function'
+ ? linkTo(record, reference)
+ : linkToRecord(rootPath, sourceId, linkTo as string);
+
+ if (linkType !== undefined) {
+ console.warn(
+ "The 'linkType' prop is deprecated and should be named to 'link' in "
+ );
+ }
+
+ const resourceLinkPath = getResourceLinkPath(
+ linkType !== undefined ? linkType : link
+ );
return {
isLoading: !referenceRecord && !allowEmpty,
diff --git a/packages/ra-ui-materialui/src/field/ReferenceField.js b/packages/ra-ui-materialui/src/field/ReferenceField.js
index 895b469940b..9a41925712a 100644
--- a/packages/ra-ui-materialui/src/field/ReferenceField.js
+++ b/packages/ra-ui-materialui/src/field/ReferenceField.js
@@ -97,24 +97,40 @@ ReferenceFieldView.propTypes = {
*
*
*
+ * @default
* By default, includes a link to the page of the related record
* (`/users/:userId` in the previous example).
*
- * Set the linkType prop to "show" to link to the page instead.
+ * Set the `link` prop to "show" to link to the page instead.
*
* @example
- *
+ *
*
*
*
+ * @default
* You can also prevent `` from adding link to children by setting
- * `linkType` to false.
+ * `link` to false.
*
* @example
- *
+ *
*
*
+ *
+ * @default
+ * Alternatively, you can also pass a custom function to `link`. It must take reference and record
+ * as arguments and return a string
+ *
+ * @example
+ * "/path/to/${reference}/${record}"}>
+ *
+ *
+ *
+ * @default
+ * In previous versions of React-Admin, the prop `linkType` was used. It is now deprecated and replaced with `link`. However
+ * backward-compatibility is still kept
*/
+
const ReferenceField = ({ children, ...props }) => {
if (React.Children.count(children) !== 1) {
throw new Error(' only accepts a single child');
@@ -149,14 +165,14 @@ ReferenceField.propTypes = {
sortBy: PropTypes.string,
source: PropTypes.string.isRequired,
translateChoice: PropTypes.func,
- linkType: PropTypes.oneOfType([PropTypes.string, PropTypes.bool])
- .isRequired,
+ linkType: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.func]),
+ link: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.func]).isRequired,
};
ReferenceField.defaultProps = {
allowEmpty: false,
classes: {},
- linkType: 'edit',
+ link: 'edit',
record: {},
};
diff --git a/packages/ra-ui-materialui/src/field/sanitizeRestProps.ts b/packages/ra-ui-materialui/src/field/sanitizeRestProps.ts
index 8eee2387f42..176bae545b2 100644
--- a/packages/ra-ui-materialui/src/field/sanitizeRestProps.ts
+++ b/packages/ra-ui-materialui/src/field/sanitizeRestProps.ts
@@ -11,6 +11,7 @@ export default (props: object): object =>
'headerClassName',
'label',
'linkType',
+ 'link',
'locale',
'record',
'resource',