Skip to content

Commit 5bd9073

Browse files
committed
add render prop on ReferenceOneFieldBase
1 parent 424c0fe commit 5bd9073

File tree

7 files changed

+201
-12
lines changed

7 files changed

+201
-12
lines changed

docs/ListBase.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ The `<ListBase>` component accepts the same props as [`useListController`](./use
6969
These are a subset of the props accepted by `<List>` - only the props that change data fetching, and not the props related to the user interface.
7070

7171
In addition, `<ListBase>` renders its children components inside a `ListContext`. Check [the `<List children>` documentation](./List.md#children) for usage examples.
72-
Alternatively you can pass a render function to the props, in place of children. This function will receive the listContext as argument. Check [the `<List render>` documentation](./List.md#render) for usage examples.
72+
Alternatively you can pass a render function to the props, in place of children. This function will receive the listContext as argument.
7373

7474
## Security
7575

packages/ra-core/src/controller/field/ReferenceManyFieldBase.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ describe('ReferenceManyFieldBase', () => {
4141
return <div>{resource}</div>;
4242
};
4343
const dataProvider = testDataProvider({
44+
// @ts-ignore
4445
getList: () =>
45-
// @ts-ignore
4646
Promise.resolve({ data: [{ id: 1 }, { id: 2 }], total: 2 }),
4747
});
4848
render(

packages/ra-core/src/controller/field/ReferenceManyFieldBase.stories.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,9 @@ export const WithRenderPagination = ({
334334
reference="books"
335335
perPage={2}
336336
renderPagination={({
337-
page,
337+
page = 0,
338338
setPage,
339-
total,
339+
total = 0,
340340
perPage,
341341
}) => {
342342
const nextPage = () => {
@@ -353,9 +353,7 @@ export const WithRenderPagination = ({
353353
>
354354
previous page
355355
</button>
356-
{(page - 1) * perPage + 1} -{' '}
357-
{Math.min(page * perPage, total)} of{' '}
358-
{total}
356+
{`${(page - 1) * perPage + 1} - ${Math.min(page * perPage, total)} of ${total}`}
359357
<button
360358
disabled={page >= total / perPage}
361359
onClick={nextPage}
@@ -420,12 +418,17 @@ const List = ({ source }: { source: string }) => {
420418
};
421419

422420
const Pagination = () => {
423-
const { page, setPage, total, perPage } = useListContextWithProps();
421+
const {
422+
page = 1,
423+
setPage,
424+
total = 0,
425+
perPage = 0,
426+
} = useListContextWithProps();
424427
const nextPage = () => {
425-
setPage(page + 1);
428+
setPage?.(page + 1);
426429
};
427430
const previousPage = () => {
428-
setPage(page - 1);
431+
setPage?.(page - 1);
429432
};
430433
return (
431434
<div>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as React from 'react';
2+
import { render, screen, waitFor } from '@testing-library/react';
3+
import {
4+
Basic,
5+
Loading,
6+
WithRenderProp,
7+
} from './ReferenceOneFieldBase.stories';
8+
9+
describe('ReferenceOneFieldBase', () => {
10+
it('should pass the loading state', async () => {
11+
jest.spyOn(console, 'error')
12+
.mockImplementationOnce(() => {})
13+
.mockImplementationOnce(() => {});
14+
15+
render(<Loading />);
16+
await waitFor(() => {
17+
expect(screen.queryByText('Loading...')).not.toBeNull();
18+
});
19+
});
20+
21+
it('should render the data', async () => {
22+
render(<Basic />);
23+
await waitFor(() => {
24+
expect(screen.queryByText('9780393966473')).not.toBeNull();
25+
});
26+
});
27+
28+
describe('with render prop', () => {
29+
it('should pass the loading state', async () => {
30+
jest.spyOn(console, 'error')
31+
.mockImplementationOnce(() => {})
32+
.mockImplementationOnce(() => {});
33+
34+
const dataProviderWithAuthorsLoading = {
35+
getOne: () =>
36+
Promise.resolve({
37+
data: {
38+
id: 1,
39+
title: 'War and Peace',
40+
author: 1,
41+
summary:
42+
"War and Peace broadly focuses on Napoleon's invasion of Russia, and the impact it had on Tsarist society. The book explores themes such as revolution, revolution and empire, the growth and decline of various states and the impact it had on their economies, culture, and society.",
43+
year: 1869,
44+
},
45+
}),
46+
getMany: _resource => new Promise(() => {}),
47+
} as any;
48+
49+
render(
50+
<WithRenderProp dataProvider={dataProviderWithAuthorsLoading} />
51+
);
52+
await waitFor(() => {
53+
expect(screen.queryByText('Loading...')).not.toBeNull();
54+
});
55+
});
56+
57+
it('should render the data', async () => {
58+
render(<WithRenderProp />);
59+
await waitFor(() => {
60+
expect(screen.queryByText('9780393966473')).not.toBeNull();
61+
});
62+
});
63+
});
64+
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import * as React from 'react';
2+
3+
import {
4+
CoreAdminContext,
5+
RecordContextProvider,
6+
ReferenceOneFieldBase,
7+
ResourceContextProvider,
8+
TestMemoryRouter,
9+
useRecordContext,
10+
useReferenceOneFieldController,
11+
} from '../..';
12+
13+
export default { title: 'ra-ui-materialui/fields/ReferenceOneFieldBase' };
14+
15+
const defaultDataProvider = {
16+
getManyReference: () =>
17+
Promise.resolve({
18+
data: [{ id: 1, ISBN: '9780393966473', genre: 'novel' }],
19+
total: 1,
20+
}),
21+
} as any;
22+
23+
const Wrapper = ({ children, dataProvider = defaultDataProvider }) => (
24+
<TestMemoryRouter initialEntries={['/books/1/show']}>
25+
<CoreAdminContext dataProvider={dataProvider}>
26+
<ResourceContextProvider value="books">
27+
<RecordContextProvider
28+
value={{ id: 1, title: 'War and Peace' }}
29+
>
30+
{children}
31+
</RecordContextProvider>
32+
</ResourceContextProvider>
33+
</CoreAdminContext>
34+
</TestMemoryRouter>
35+
);
36+
37+
export const Basic = () => (
38+
<Wrapper>
39+
<ReferenceOneFieldBase reference="book_details" target="book_id">
40+
<MyReferenceOneField reference="book_details" target="book_id">
41+
<TextField source="ISBN" />
42+
</MyReferenceOneField>
43+
</ReferenceOneFieldBase>
44+
</Wrapper>
45+
);
46+
47+
const dataProviderWithLoading = {
48+
getManyReference: () => new Promise(() => {}),
49+
} as any;
50+
51+
export const Loading = () => (
52+
<Wrapper dataProvider={dataProviderWithLoading}>
53+
<ReferenceOneFieldBase reference="book_details" target="book_id">
54+
<MyReferenceOneField reference="book_details" target="book_id">
55+
<TextField source="ISBN" />
56+
</MyReferenceOneField>
57+
</ReferenceOneFieldBase>
58+
</Wrapper>
59+
);
60+
61+
export const WithRenderProp = ({
62+
dataProvider = defaultDataProvider,
63+
}: {
64+
dataProvider?: any;
65+
}) => {
66+
return (
67+
<Wrapper dataProvider={dataProvider}>
68+
<ReferenceOneFieldBase
69+
reference="book_details"
70+
target="book_id"
71+
render={({ isPending, error, referenceRecord }) => {
72+
if (isPending) {
73+
return <p>Loading...</p>;
74+
}
75+
76+
if (error) {
77+
return (
78+
<p style={{ color: 'red' }}>{error.toString()}</p>
79+
);
80+
}
81+
return (
82+
<span>
83+
{referenceRecord ? referenceRecord.ISBN : ''}
84+
</span>
85+
);
86+
}}
87+
/>
88+
</Wrapper>
89+
);
90+
};
91+
92+
const MyReferenceOneField = ({
93+
reference,
94+
target,
95+
children,
96+
}: {
97+
children: React.ReactNode;
98+
reference: string;
99+
target: string;
100+
}) => {
101+
const context = useReferenceOneFieldController({
102+
reference,
103+
target,
104+
});
105+
106+
if (context.isPending) {
107+
return <p>Loading...</p>;
108+
}
109+
110+
if (context.error) {
111+
return <p style={{ color: 'red' }}>{context.error.toString()}</p>;
112+
}
113+
return children;
114+
};
115+
116+
const TextField = ({ source }) => {
117+
const record = useRecordContext();
118+
return <span>{record ? record[source] : ''}</span>;
119+
};

packages/ra-core/src/controller/field/ReferenceOneFieldBase.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useGetPathForRecord } from '../../routing';
1010
import type { UseReferenceFieldControllerResult } from './useReferenceFieldController';
1111
import type { RaRecord } from '../../types';
1212
import type { LinkToType } from '../../routing';
13+
import { UseReferenceResult } from '../useReference';
1314

1415
/**
1516
* Render the related record in a one-to-one relationship
@@ -29,6 +30,7 @@ export const ReferenceOneFieldBase = <
2930
) => {
3031
const {
3132
children,
33+
render,
3234
record,
3335
reference,
3436
source = 'id',
@@ -79,7 +81,7 @@ export const ReferenceOneFieldBase = <
7981
<ResourceContextProvider value={reference}>
8082
<ReferenceFieldContextProvider value={context}>
8183
<RecordContextProvider value={context.referenceRecord}>
82-
{children}
84+
{render ? render(controllerProps) : children}
8385
</RecordContextProvider>
8486
</ReferenceFieldContextProvider>
8587
</ResourceContextProvider>
@@ -94,6 +96,7 @@ export interface ReferenceOneFieldBaseProps<
9496
ReferenceRecordType
9597
> {
9698
children?: ReactNode;
99+
render?: (props: UseReferenceResult<ReferenceRecordType>) => ReactNode;
97100
link?: LinkToType<ReferenceRecordType>;
98101
empty?: ReactNode;
99102
resource?: string;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,6 @@ export const ReferenceManyField = <
8080

8181
export interface ReferenceManyFieldProps<
8282
RecordType extends Record<string, any> = Record<string, any>,
83-
ReferenceRecordType extends Record<string, any> = Record<string, any>,
83+
ReferenceRecordType extends RaRecord = RaRecord,
8484
> extends Omit<FieldProps<RecordType>, 'source'>,
8585
ReferenceManyFieldBaseProps<RecordType, ReferenceRecordType> {}

0 commit comments

Comments
 (0)