Skip to content

Commit 21f0db9

Browse files
committed
add render props to EditBase component
1 parent f20bafc commit 21f0db9

File tree

4 files changed

+82
-10
lines changed

4 files changed

+82
-10
lines changed

docs/EditBase.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const BookEdit = () => (
4747
You can customize the `<EditBase>` component using the following props, documented in the `<Edit>` component:
4848

4949
* `children`: the components that renders the form
50+
* `render`: alternative to children, a function that takes the EditController context and renders the form
5051
* [`disableAuthentication`](./Edit.md#disableauthentication): disable the authentication check
5152
* [`id`](./Edit.md#id): the id of the record to edit
5253
* [`mutationMode`](./Edit.md#mutationmode): switch to optimistic or pessimistic mutations (undoable by default)

packages/ra-core/src/controller/edit/EditBase.spec.tsx

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import {
88
DefaultTitle,
99
NoAuthProvider,
1010
WithAuthProviderNoAccessControl,
11+
WithRenderProps,
1112
} from './EditBase.stories';
1213

1314
describe('EditBase', () => {
1415
it('should give access to the save function', async () => {
1516
const dataProvider = testDataProvider({
16-
// @ts-ignore
1717
getOne: () =>
18+
// @ts-ignore
1819
Promise.resolve({ data: { id: 12, test: 'previous' } }),
1920
update: jest.fn((_, { id, data, previousData }) =>
2021
Promise.resolve({ data: { id, ...previousData, ...data } })
@@ -44,8 +45,8 @@ describe('EditBase', () => {
4445

4546
it('should allow to override the onSuccess function', async () => {
4647
const dataProvider = testDataProvider({
47-
// @ts-ignore
4848
getOne: () =>
49+
// @ts-ignore
4950
Promise.resolve({ data: { id: 12, test: 'previous' } }),
5051
update: jest.fn((_, { id, data, previousData }) =>
5152
Promise.resolve({ data: { id, ...previousData, ...data } })
@@ -84,8 +85,8 @@ describe('EditBase', () => {
8485

8586
it('should allow to override the onSuccess function at call time', async () => {
8687
const dataProvider = testDataProvider({
87-
// @ts-ignore
8888
getOne: () =>
89+
// @ts-ignore
8990
Promise.resolve({ data: { id: 12, test: 'previous' } }),
9091
update: jest.fn((_, { id, data, previousData }) =>
9192
Promise.resolve({ data: { id, ...previousData, ...data } })
@@ -128,8 +129,8 @@ describe('EditBase', () => {
128129
it('should allow to override the onError function', async () => {
129130
jest.spyOn(console, 'error').mockImplementation(() => {});
130131
const dataProvider = testDataProvider({
131-
// @ts-ignore
132132
getOne: () =>
133+
// @ts-ignore
133134
Promise.resolve({ data: { id: 12, test: 'previous' } }),
134135
// @ts-ignore
135136
update: jest.fn(() => Promise.reject({ message: 'test' })),
@@ -162,8 +163,8 @@ describe('EditBase', () => {
162163

163164
it('should allow to override the onError function at call time', async () => {
164165
const dataProvider = testDataProvider({
165-
// @ts-ignore
166166
getOne: () =>
167+
// @ts-ignore
167168
Promise.resolve({ data: { id: 12, test: 'previous' } }),
168169
// @ts-ignore
169170
update: jest.fn(() => Promise.reject({ message: 'test' })),
@@ -199,8 +200,8 @@ describe('EditBase', () => {
199200

200201
it('should allow to override the transform function', async () => {
201202
const dataProvider = testDataProvider({
202-
// @ts-ignore
203203
getOne: () =>
204+
// @ts-ignore
204205
Promise.resolve({ data: { id: 12, test: 'previous' } }),
205206
update: jest.fn((_, { id, data, previousData }) =>
206207
Promise.resolve({ data: { id, ...previousData, ...data } })
@@ -239,8 +240,8 @@ describe('EditBase', () => {
239240

240241
it('should allow to override the transform function at call time', async () => {
241242
const dataProvider = testDataProvider({
242-
// @ts-ignore
243243
getOne: () =>
244+
// @ts-ignore
244245
Promise.resolve({ data: { id: 12, test: 'previous' } }),
245246
update: jest.fn((_, { id, data, previousData }) =>
246247
Promise.resolve({ data: { id, ...previousData, ...data } })
@@ -376,4 +377,32 @@ describe('EditBase', () => {
376377
fireEvent.click(screen.getByText('FR'));
377378
await screen.findByText("Modifier l'article Hello (fr)");
378379
});
380+
381+
it('should allow renderProp', async () => {
382+
const dataProvider = testDataProvider({
383+
getOne: () =>
384+
// @ts-ignore
385+
Promise.resolve({ data: { id: 12, test: 'Hello' } }),
386+
update: jest.fn((_, { id, data, previousData }) =>
387+
Promise.resolve({ data: { id, ...previousData, ...data } })
388+
),
389+
});
390+
render(
391+
<WithRenderProps
392+
dataProvider={dataProvider}
393+
mutationMode="pessimistic"
394+
/>
395+
);
396+
await screen.findByText('12');
397+
await screen.findByText('Hello');
398+
fireEvent.click(screen.getByText('save'));
399+
400+
await waitFor(() => {
401+
expect(dataProvider.update).toHaveBeenCalledWith('posts', {
402+
id: 12,
403+
data: { test: 'test' },
404+
previousData: { id: 12, test: 'Hello' },
405+
});
406+
});
407+
});
379408
});

packages/ra-core/src/controller/edit/EditBase.stories.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
mergeTranslations,
1717
useEditContext,
1818
useLocaleState,
19+
MutationMode,
1920
} from '../..';
2021

2122
export default {
@@ -149,6 +150,35 @@ export const AccessControl = ({
149150
</CoreAdminContext>
150151
);
151152

153+
export const WithRenderProps = ({
154+
dataProvider = defaultDataProvider,
155+
mutationMode = 'optimistic',
156+
}: {
157+
dataProvider?: DataProvider;
158+
mutationMode?: MutationMode;
159+
}) => (
160+
<CoreAdminContext dataProvider={dataProvider}>
161+
<EditBase
162+
mutationMode={mutationMode}
163+
{...defaultProps}
164+
render={({ record, save }) => {
165+
const handleClick = () => {
166+
if (!save) return;
167+
168+
save({ test: 'test' });
169+
};
170+
return (
171+
<>
172+
<p>{record?.id}</p>
173+
<p>{record?.test}</p>
174+
<button onClick={handleClick}>save</button>
175+
</>
176+
);
177+
}}
178+
/>
179+
</CoreAdminContext>
180+
);
181+
152182
const defaultDataProvider = testDataProvider({
153183
getOne: () =>
154184
// @ts-ignore

packages/ra-core/src/controller/edit/EditBase.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import * as React from 'react';
22
import { ReactNode } from 'react';
33

44
import { RaRecord } from '../../types';
5-
import { useEditController, EditControllerProps } from './useEditController';
5+
import {
6+
useEditController,
7+
EditControllerProps,
8+
EditControllerResult,
9+
} from './useEditController';
610
import { EditContextProvider } from './EditContextProvider';
711
import { OptionalResourceContextProvider } from '../../core';
812
import { useIsAuthPending } from '../../auth';
@@ -38,6 +42,7 @@ import { useIsAuthPending } from '../../auth';
3842
*/
3943
export const EditBase = <RecordType extends RaRecord = any, ErrorType = Error>({
4044
children,
45+
render,
4146
loading = null,
4247
...props
4348
}: EditBaseProps<RecordType, ErrorType>) => {
@@ -52,11 +57,17 @@ export const EditBase = <RecordType extends RaRecord = any, ErrorType = Error>({
5257
return loading;
5358
}
5459

60+
if (!render && !children) {
61+
throw new Error(
62+
"<EditBase> requires either a 'render' prop or 'children' prop"
63+
);
64+
}
65+
5566
return (
5667
// We pass props.resource here as we don't need to create a new ResourceContext if the props is not provided
5768
<OptionalResourceContextProvider value={props.resource}>
5869
<EditContextProvider value={controllerProps}>
59-
{children}
70+
{render ? render(controllerProps) : children}
6071
</EditContextProvider>
6172
</OptionalResourceContextProvider>
6273
);
@@ -66,6 +77,7 @@ export interface EditBaseProps<
6677
RecordType extends RaRecord = RaRecord,
6778
ErrorType = Error,
6879
> extends EditControllerProps<RecordType, ErrorType> {
69-
children: ReactNode;
80+
children?: ReactNode;
81+
render?: (props: EditControllerResult<RecordType, ErrorType>) => ReactNode;
7082
loading?: ReactNode;
7183
}

0 commit comments

Comments
 (0)