Skip to content

Commit 85bab6b

Browse files
committed
add render prop on ReferenceField
1 parent dc6969e commit 85bab6b

File tree

5 files changed

+112
-9
lines changed

5 files changed

+112
-9
lines changed

docs/ReferenceField.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ It uses `dataProvider.getMany()` instead of `dataProvider.getOne()` [for perform
7575
| `source` | Required | `string` | - | Name of the property to display |
7676
| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'posts' |
7777
| `children` | Optional | `ReactNode` | - | One or more Field elements used to render the referenced record |
78+
| `render` | Optional | (referenceFieldContext) => `ReactNode` | - | A function used to render the referenced record, receive the reference field context as its argument |
7879
| `empty` | Optional | `ReactNode` | - | What to render when the field has no value or when the reference is missing |
7980
| `label` | Optional | `string | Function` | `resources. [resource]. fields.[source]` | Label to use for the field when rendered in layout components |
8081
| `link` | Optional | `string | Function` | `edit` | Target of the link wrapping the rendered child. Set to `false` to disable the link. |
@@ -83,6 +84,29 @@ It uses `dataProvider.getMany()` instead of `dataProvider.getOne()` [for perform
8384

8485
`<ReferenceField>` also accepts the [common field props](./Fields.md#common-field-props).
8586

87+
## `render`
88+
89+
Alternatively you can pass a render prop instead of children to be able to inline the rendering. The render function will then receive the reference field context directly.
90+
91+
```jsx
92+
export const MyReferenceField = () => (
93+
<ReferenceField source="user_id" reference="users" render={({ error, isPending, referenceRecord }) => {
94+
if (isPending) {
95+
return <p>Loading...</p>;
96+
}
97+
98+
if (error) {
99+
return (
100+
<p className="error">
101+
{error.message}
102+
</p>
103+
);
104+
}
105+
return <p>{referenceRecord.name}</p>;
106+
}} />
107+
);
108+
```
109+
86110
## `empty`
87111

88112
`<ReferenceField>` can display a custom message when the referenced record is missing, thanks to the `empty` prop.

docs/ReferenceFieldBase.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export const MyReferenceField = () => (
118118
<ReferenceFieldBase
119119
source="user_id"
120120
reference="users"
121-
render={({ error, isPending }) => {
121+
render={({ error, isPending, referenceRecord }) => {
122122
if (isPending) {
123123
return <p>Loading...</p>;
124124
}
@@ -130,7 +130,7 @@ export const MyReferenceField = () => (
130130
</p>
131131
);
132132
}
133-
return <p>{value}</p>;
133+
return <p>{referenceRecord.name}</p>;
134134
}}
135135
/>
136136
);

packages/ra-ui-materialui/src/field/ReferenceField.spec.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,46 @@ describe('<ReferenceField />', () => {
506506
await screen.findByText('boo');
507507
});
508508

509+
it('should render its child using render prop when given', async () => {
510+
const dataProvider = testDataProvider({
511+
getMany: jest.fn().mockResolvedValue({
512+
data: [{ id: 123, title: 'foo' }],
513+
}),
514+
});
515+
render(
516+
<ThemeProvider theme={theme}>
517+
<CoreAdminContext dataProvider={dataProvider}>
518+
<ResourceDefinitionContextProvider
519+
definitions={{
520+
posts: {
521+
name: 'posts',
522+
hasEdit: true,
523+
},
524+
}}
525+
>
526+
<RecordContextProvider value={record}>
527+
<ReferenceField
528+
resource="comments"
529+
source="postId"
530+
reference="posts"
531+
render={({ referenceRecord }) =>
532+
referenceRecord?.title || 'No title'
533+
}
534+
/>
535+
</RecordContextProvider>
536+
</ResourceDefinitionContextProvider>
537+
</CoreAdminContext>
538+
</ThemeProvider>
539+
);
540+
await new Promise(resolve => setTimeout(resolve, 10));
541+
expect(screen.queryByRole('progressbar')).toBeNull();
542+
expect(screen.getByText('foo')).not.toBeNull();
543+
expect(screen.queryAllByRole('link')).toHaveLength(1);
544+
expect(screen.queryByRole('link')?.getAttribute('href')).toBe(
545+
'#/posts/123'
546+
);
547+
});
548+
509549
describe('link', () => {
510550
it('should render a link to specified link type', async () => {
511551
render(<LinkShow />);

packages/ra-ui-materialui/src/field/ReferenceField.stories.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,3 +951,23 @@ export const Themed = () => {
951951
</Wrapper>
952952
);
953953
};
954+
955+
export const WithRenderProp = () => (
956+
<Wrapper>
957+
<ReferenceField
958+
source="detail_id"
959+
reference="book_details"
960+
render={({ error, isPending, referenceRecord }) => {
961+
console.log({ error, isPending, referenceRecord });
962+
if (isPending) {
963+
return <p>Loading...</p>;
964+
}
965+
966+
if (error) {
967+
return <p style={{ color: 'red' }}>{error.message}</p>;
968+
}
969+
return referenceRecord.ISBN;
970+
}}
971+
></ReferenceField>
972+
</Wrapper>
973+
);

packages/ra-ui-materialui/src/field/ReferenceField.tsx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
type RaRecord,
1717
ReferenceFieldBase,
1818
useReferenceFieldContext,
19+
UseReferenceFieldControllerResult,
1920
} from 'ra-core';
2021
import type { UseQueryOptions } from '@tanstack/react-query';
2122
import clsx from 'clsx';
@@ -40,6 +41,14 @@ import { visuallyHidden } from '@mui/utils';
4041
* <TextField source="name" />
4142
* </ReferenceField>
4243
*
44+
* @example // using a render prop to render the record
45+
* <ReferenceField label="User" source="userId" reference="users" render={
46+
* (context) => (
47+
* <p>{context.referenceRecord?.name}</p>
48+
* )
49+
* }>
50+
* </ReferenceField>
51+
*
4352
* @example // By default, includes a link to the <Edit> page of the related record
4453
* // (`/users/:userId` in the previous example).
4554
* // Set the `link` prop to "show" to link to the <Show> page instead.
@@ -60,9 +69,11 @@ import { visuallyHidden } from '@mui/utils';
6069
export const ReferenceField = <
6170
RecordType extends Record<string, any> = Record<string, any>,
6271
ReferenceRecordType extends RaRecord = RaRecord,
63-
>(
64-
inProps: ReferenceFieldProps<RecordType, ReferenceRecordType>
65-
) => {
72+
>({
73+
children,
74+
render,
75+
...inProps
76+
}: ReferenceFieldProps<RecordType, ReferenceRecordType>) => {
6677
const props = useThemeProps({
6778
props: inProps,
6879
name: PREFIX,
@@ -89,6 +100,8 @@ export const ReferenceField = <
89100
>
90101
<PureReferenceFieldView<RecordType, ReferenceRecordType>
91102
{...props}
103+
render={render}
104+
children={children}
92105
/>
93106
</ReferenceFieldBase>
94107
);
@@ -99,6 +112,9 @@ export interface ReferenceFieldProps<
99112
ReferenceRecordType extends RaRecord = RaRecord,
100113
> extends FieldProps<RecordType> {
101114
children?: ReactNode;
115+
render?: (
116+
context: UseReferenceFieldControllerResult<ReferenceRecordType>
117+
) => ReactNode;
102118
/**
103119
* @deprecated Use the empty prop instead
104120
*/
@@ -123,13 +139,13 @@ export const ReferenceFieldView = <
123139
>(
124140
props: ReferenceFieldViewProps<RecordType, ReferenceRecordType>
125141
) => {
126-
const { children, className, emptyText, reference, sx, ...rest } =
142+
const { children, render, className, emptyText, reference, sx, ...rest } =
127143
useThemeProps({
128144
props: props,
129145
name: PREFIX,
130146
});
131-
const { error, link, isLoading, referenceRecord } =
132-
useReferenceFieldContext();
147+
const referenceFieldContext = useReferenceFieldContext();
148+
const { error, link, isLoading, referenceRecord } = referenceFieldContext;
133149

134150
const getRecordRepresentation = useGetRecordRepresentation(reference);
135151

@@ -150,7 +166,7 @@ export const ReferenceFieldView = <
150166
return <LinearProgress />;
151167
}
152168

153-
const child = children || (
169+
const child = (render ? render(referenceFieldContext) : children) || (
154170
<Typography component="span" variant="body2">
155171
{getRecordRepresentation(referenceRecord)}
156172
</Typography>
@@ -192,6 +208,9 @@ export interface ReferenceFieldViewProps<
192208
> extends FieldProps<RecordType>,
193209
Omit<ReferenceFieldProps<RecordType, ReferenceRecordType>, 'link'> {
194210
children?: ReactNode;
211+
render?: (
212+
context: UseReferenceFieldControllerResult<RaRecord>
213+
) => ReactNode;
195214
reference: string;
196215
resource?: string;
197216
translateChoice?: Function | boolean;

0 commit comments

Comments
 (0)