Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] Add link function to ReferenceField #3282

Merged
merged 6 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ If you're using a Custom App, you had to render Resource components with the reg
+ <Resource name="users" intent="registration" />
```

## 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
- <ReferenceField resource="comments" record={data[id]} source="post_id" reference="posts" basePath={basePath} linkType="show">
+ <ReferenceField resource="comments" record={data[id]} source="post_id" reference="posts" basePath={basePath} link="show">
```


## `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.
Expand Down
17 changes: 13 additions & 4 deletions docs/Fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,10 +495,10 @@ With this configuration, `<ReferenceField>` wraps the user's name in a link to t
</Admin>
```

To change the link from the `<Edit>` page to the `<Show>` page, set the `linkType` prop to "show".
To change the link from the `<Edit>` page to the `<Show>` page, set the `link` prop to "show".

```jsx
<ReferenceField label="User" source="userId" reference="users" linkType="show">
<ReferenceField label="User" source="userId" reference="users" link="show">
<TextField source="name" />
</ReferenceField>
```
Expand All @@ -511,11 +511,20 @@ By default, `<ReferenceField>` is sorted by its `source`. To specify another att
</ReferenceField>
```

You can also prevent `<ReferenceField>` from adding link to children by setting `linkType` to `false`.
You can also prevent `<ReferenceField>` from adding link to children by setting `link` to `false`.

```jsx
// No link
<ReferenceField label="User" source="userId" reference="users" linkType={false}>
<ReferenceField label="User" source="userId" reference="users" link={false}>
<TextField source="name" />
</ReferenceField>
```

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
<ReferenceField label="User" source="userId" reference="users" link={(record, reference) => `/my/path/to/${reference}/${record.id}`}>
<TextField source="name" />
</ReferenceField>
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ describe('<ReferenceFieldController />', () => {
});
});

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(<span>children</span>);
renderWithRedux(
<ReferenceFieldController
Expand All @@ -165,7 +165,7 @@ describe('<ReferenceFieldController />', () => {
resource="comments"
reference="posts"
basePath="/comments"
linkType="show"
link="show"
>
{children}
</ReferenceFieldController>,
Expand All @@ -179,7 +179,7 @@ describe('<ReferenceFieldController />', () => {
});
});

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(<span>children</span>);
renderWithRedux(
<ReferenceFieldController
Expand All @@ -188,7 +188,7 @@ describe('<ReferenceFieldController />', () => {
reference="edit"
resource="show"
basePath="/show"
linkType="show"
link="show"
>
{children}
</ReferenceFieldController>,
Expand All @@ -210,7 +210,7 @@ describe('<ReferenceFieldController />', () => {
});
});

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(<span>children</span>);
renderWithRedux(
<ReferenceFieldController
Expand All @@ -220,7 +220,7 @@ describe('<ReferenceFieldController />', () => {
resource="edit"
basePath="/edit"
crudGetManyAccumulate={crudGetManyAccumulate}
linkType="show"
link="show"
>
{children}
</ReferenceFieldController>,
Expand All @@ -242,15 +242,15 @@ describe('<ReferenceFieldController />', () => {
});
});

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(<span>children</span>);
renderWithRedux(
<ReferenceFieldController
record={{ id: 1, postId: 123 }}
source="postId"
reference="posts"
basePath="/foo"
linkType={false}
link={false}
>
{children}
</ReferenceFieldController>,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -11,7 +11,7 @@ interface Props {
reference: string;
resource: string;
source: string;
linkType: string | boolean;
link: string | boolean | LinkToFunctionType;
}

/**
Expand All @@ -28,18 +28,18 @@ interface Props {
* By default, includes a link to the <Edit> page of the related record
* (`/users/:userId` in the previous example).
*
* Set the linkType prop to "show" to link to the <Show> page instead.
* Set the link prop to "show" to link to the <Show> page instead.
*
* @example
* <ReferenceField label="User" source="userId" reference="users" linkType="show">
* <ReferenceField label="User" source="userId" reference="users" link="show">
* <TextField source="name" />
* </ReferenceField>
*
* You can also prevent `<ReferenceField>` from adding link to children by setting
* `linkType` to false.
* `link` to false.
*
* @example
* <ReferenceField label="User" source="userId" reference="users" linkType={false}>
* <ReferenceField label="User" source="userId" reference="users" link={false}>
* <TextField source="name" />
* </ReferenceField>
*/
Expand Down
35 changes: 28 additions & 7 deletions packages/ra-core/src/controller/field/useReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ 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;
record?: Record;
reference: string;
resource: string;
source: string;
linkType: string | boolean;
link: LinkToType;
linkType?: LinkToType; // deprecated, use link instead
}

export interface UseReferenceProps {
Expand All @@ -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)
*/

/**
Expand All @@ -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
Expand All @@ -61,7 +67,8 @@ export interface UseReferenceProps {
export const useReference = ({
allowEmpty = false,
basePath,
linkType = 'edit',
link = 'edit',
linkType,
record = { id: '' },
reference,
resource,
Expand All @@ -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 <ReferenceField />"
);
}

const resourceLinkPath = getResourceLinkPath(
linkType !== undefined ? linkType : link
);

return {
isLoading: !referenceRecord && !allowEmpty,
Expand Down
30 changes: 23 additions & 7 deletions packages/ra-ui-materialui/src/field/ReferenceField.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,24 +97,40 @@ ReferenceFieldView.propTypes = {
* <TextField source="name" />
* </ReferenceField>
*
* @default
* By default, includes a link to the <Edit> page of the related record
* (`/users/:userId` in the previous example).
*
* Set the linkType prop to "show" to link to the <Show> page instead.
* Set the `link` prop to "show" to link to the <Show> page instead.
*
* @example
* <ReferenceField label="User" source="userId" reference="users" linkType="show">
* <ReferenceField label="User" source="userId" reference="users" link="show">
* <TextField source="name" />
* </ReferenceField>
*
* @default
* You can also prevent `<ReferenceField>` from adding link to children by setting
* `linkType` to false.
* `link` to false.
*
* @example
* <ReferenceField label="User" source="userId" reference="users" linkType={false}>
* <ReferenceField label="User" source="userId" reference="users" link={false}>
* <TextField source="name" />
* </ReferenceField>
*
* @default
* Alternatively, you can also pass a custom function to `link`. It must take reference and record
* as arguments and return a string
*
* @example
* <ReferenceField label="User" source="userId" reference="users" link={(reference, record) => "/path/to/${reference}/${record}"}>
* <TextField source="name" />
* </ReferenceField>
*
* @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('<ReferenceField> only accepts a single child');
Expand Down Expand Up @@ -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: {},
};

Expand Down
1 change: 1 addition & 0 deletions packages/ra-ui-materialui/src/field/sanitizeRestProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default (props: object): object =>
'headerClassName',
'label',
'linkType',
'link',
'locale',
'record',
'resource',
Expand Down