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',