Skip to content

Commit 487d255

Browse files
committed
add render prop to Create
1 parent 3ab52c5 commit 487d255

File tree

10 files changed

+124
-22
lines changed

10 files changed

+124
-22
lines changed

docs/Create.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ You can customize the `<Create>` component using the following props:
5858
* [`actions`](#actions): override the actions toolbar with a custom component
5959
* [`aside`](#aside): component to render aside to the main content
6060
* `children`: the components that renders the form
61+
* `render`: Alternative to children. A function that renders the form, receive the create context as its argument
6162
* `className`: passed to the root component
6263
* [`component`](#component): override the root component
6364
* [`disableAuthentication`](#disableauthentication): disable the authentication check
@@ -70,6 +71,36 @@ You can customize the `<Create>` component using the following props:
7071
* [`title`](#title): override the page title
7172
* [`transform`](#transform): transform the form data before calling `dataProvider.create()`
7273

74+
## `render`
75+
76+
Alternatively to children you can pass a render prop to `<Create>`. The render prop will receive the create context as its argument, allowing to inline the render logic for the create form.
77+
When receiving a render prop the `<Create>` component will ignore the children property.
78+
79+
{% raw %}
80+
```tsx
81+
<Create render={(createContext) => {
82+
if (createContext.isPending) {
83+
return <div>Loading...</div>;
84+
}
85+
if (createContext.error) {
86+
return <div>Error: {error.message}</div>;
87+
}
88+
89+
return (
90+
<Box>
91+
<h1>{`Create new ${createContext.resource}`}</h1>
92+
<SimpleForm>
93+
<TextInput source="title" validate={[required()]} />
94+
<TextInput source="teaser" multiline={true} label="Short description" />
95+
<RichTextInput source="body" />
96+
<DateInput label="Publication date" source="published_at" defaultValue={new Date()} />
97+
</SimpleForm>
98+
</Box>
99+
);
100+
}} />
101+
```
102+
{% endraw %}
103+
73104
## `actions`
74105

75106
You can replace the list of default actions by your own elements using the `actions` prop:

docs/Edit.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,25 +83,39 @@ You can customize the `<Edit>` component using the following props:
8383

8484
## `render`
8585

86-
Alternatively to children you can pass a render prop to `<List>`. The render prop will receive the list context as its argument, allowing to inline the render logic for both the list content.
87-
When receiving a render prop the `<List>` component will ignore the children property.
86+
Alternatively to children you can pass a render prop to `<Edit>`. The render prop will receive the edit context as its argument, allowing to inline the render logic for the edit form.
87+
When receiving a render prop the `<Edit>` component will ignore the children property.
8888

8989
{% raw %}
9090
```tsx
91-
<List
92-
render={({ error, isPending }) => {
93-
if (isPending) {
91+
<Edit
92+
render={(listContext) => {
93+
if (listContext.isPending) {
9494
return <div>Loading...</div>;
9595
}
96-
if (error) {
96+
if (listContext.error) {
9797
return <div>Error: {error.message}</div>;
9898
}
9999
return (
100-
<SimpleList
101-
primaryText="%{title} (%{year})"
102-
secondaryText="%{summary}"
103-
tertiaryText={record => record.year}
104-
/>
100+
<div>
101+
<h1>{`Edit ${listController.resource} #${listController.record.id}`}</h1>
102+
<SimpleForm>
103+
<TextInput disabled label="Id" source="id" />
104+
<TextInput source="title" validate={required()} />
105+
<TextInput multiline source="teaser" validate={required()} />
106+
<RichTextInput source="body" validate={required()} />
107+
<DateInput label="Publication date" source="published_at" />
108+
<ReferenceManyField label="Comments" reference="comments" target="post_id">
109+
<DataTable>
110+
<DataTable.Col source="body" />
111+
<DataTable.Col source="created_at" field={DateField} />
112+
<DataTable.Col>
113+
<EditButton />
114+
</DataTable.Col>
115+
</DataTable>
116+
</ReferenceManyField>
117+
</SimpleForm>
118+
</div>
105119
);
106120
}}
107121
/>

docs/List.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Additional props are passed down to the root component (a MUI `<Card>` by defaul
8282

8383
## `render`
8484

85-
Alternatively to children you can pass a render prop to `<List>`. The render prop will receive the list context as its argument, allowing to inline the render logic for both the list content.
85+
Alternatively to children you can pass a render prop to `<List>`. The render prop will receive the list context as its argument, allowing to inline the render logic for the list content.
8686
When receiving a render prop the `<List>` component will ignore the children property.
8787

8888
{% raw %}

packages/ra-ui-materialui/src/detail/Create.spec.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import expect from 'expect';
33
import { CoreAdminContext, testDataProvider } from 'ra-core';
4-
import { screen, render } from '@testing-library/react';
4+
import { screen, render, waitFor } from '@testing-library/react';
55

66
import { Create } from './Create';
77
import {
@@ -12,6 +12,7 @@ import {
1212
NotificationDefault,
1313
NotificationTranslated,
1414
Themed,
15+
WithRenderProp,
1516
} from './Create.stories';
1617

1718
describe('<Create />', () => {
@@ -57,6 +58,13 @@ describe('<Create />', () => {
5758
);
5859
});
5960

61+
it('should support a render prop', async () => {
62+
render(<WithRenderProp />);
63+
await waitFor(() => {
64+
expect(screen.queryByText('Create new books')).not.toBeNull();
65+
});
66+
});
67+
6068
describe('title', () => {
6169
it('should display by default the title of the resource', async () => {
6270
render(<Basic />);

packages/ra-ui-materialui/src/detail/Create.stories.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,22 @@ export const Themed = () => (
305305
</Admin>
306306
</TestMemoryRouter>
307307
);
308+
309+
export const WithRenderProp = () => (
310+
<TestMemoryRouter initialEntries={['/books/create']}>
311+
<Admin dataProvider={dataProvider}>
312+
<Resource
313+
name="books"
314+
create={() => (
315+
<Create
316+
render={createContext => {
317+
return (
318+
<div>{`Create new ${createContext.resource}`}</div>
319+
);
320+
}}
321+
/>
322+
)}
323+
/>
324+
</Admin>
325+
</TestMemoryRouter>
326+
);

packages/ra-ui-materialui/src/detail/Create.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { Loading } from '../layout';
2222
*
2323
* The <Create> component accepts the following props:
2424
*
25+
* - children: Component rendering the Form Layout
26+
* - render: Alternative to children. A function to render the Form Layout. Receives the create context as its argument.
2527
* - actions
2628
* - aside
2729
* - component
@@ -66,7 +68,6 @@ export const Create = <
6668
name: PREFIX,
6769
});
6870

69-
useCheckMinimumRequiredProps('Create', ['children'], props);
7071
const {
7172
resource,
7273
record,
@@ -80,6 +81,13 @@ export const Create = <
8081
loading = defaultLoading,
8182
...rest
8283
} = props;
84+
85+
if (!props.render && !props.children) {
86+
throw new Error(
87+
'<Create> requires either a `render` prop or `children` prop'
88+
);
89+
}
90+
8391
return (
8492
<CreateBase<RecordType, ResultRecordType>
8593
resource={resource}
@@ -102,8 +110,11 @@ export interface CreateProps<
102110
RecordType extends Omit<RaRecord, 'id'> = any,
103111
MutationOptionsError = Error,
104112
ResultRecordType extends RaRecord = RecordType & { id: Identifier },
105-
> extends CreateBaseProps<RecordType, ResultRecordType, MutationOptionsError>,
106-
Omit<CreateViewProps, 'children'> {}
113+
> extends Omit<
114+
CreateBaseProps<RecordType, ResultRecordType, MutationOptionsError>,
115+
'children' | 'render'
116+
>,
117+
CreateViewProps {}
107118

108119
const defaultLoading = <Loading />;
109120

packages/ra-ui-materialui/src/detail/CreateView.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import * as React from 'react';
2-
import type { ElementType, ReactElement } from 'react';
2+
import type { ElementType, ReactElement, ReactNode } from 'react';
33
import {
44
Card,
55
type ComponentsOverrides,
66
styled,
77
type SxProps,
88
type Theme,
99
} from '@mui/material';
10-
import { useCreateContext } from 'ra-core';
10+
import { CreateControllerResult, useCreateContext } from 'ra-core';
1111
import clsx from 'clsx';
1212

1313
import { Title } from '../layout';
@@ -18,13 +18,16 @@ export const CreateView = (props: CreateViewProps) => {
1818
actions,
1919
aside,
2020
children,
21+
render,
2122
className,
2223
component: Content = Card,
2324
title,
2425
...rest
2526
} = props;
2627

27-
const { resource, defaultTitle } = useCreateContext();
28+
const createContext = useCreateContext();
29+
30+
const { resource, defaultTitle } = createContext;
2831

2932
return (
3033
<Root className={clsx('create-page', className)} {...rest}>
@@ -41,7 +44,9 @@ export const CreateView = (props: CreateViewProps) => {
4144
[CreateClasses.noActions]: !actions,
4245
})}
4346
>
44-
<Content className={CreateClasses.card}>{children}</Content>
47+
<Content className={CreateClasses.card}>
48+
{render ? render(createContext) : children}
49+
</Content>
4550
{aside}
4651
</div>
4752
</Root>
@@ -55,6 +60,8 @@ export interface CreateViewProps
5560
component?: ElementType;
5661
sx?: SxProps<Theme>;
5762
title?: string | ReactElement | false;
63+
render?: (createContext: CreateControllerResult) => ReactNode;
64+
children?: ReactNode;
5865
}
5966

6067
const PREFIX = 'RaCreate';

packages/ra-ui-materialui/src/detail/Edit.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export const Edit = <RecordType extends RaRecord = any>(
6565
name: PREFIX,
6666
});
6767

68-
useCheckMinimumRequiredProps('Edit', ['children'], props);
6968
const {
7069
resource,
7170
id,
@@ -79,6 +78,12 @@ export const Edit = <RecordType extends RaRecord = any>(
7978
...rest
8079
} = props;
8180

81+
if (!props.render && !props.children) {
82+
throw new Error(
83+
'<Edit> requires either a `render` prop or `children` prop'
84+
);
85+
}
86+
8287
return (
8388
<EditBase<RecordType>
8489
resource={resource}

packages/ra-ui-materialui/src/detail/EditView.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ export const EditView = (props: EditViewProps) => {
4141

4242
const finalActions =
4343
typeof actions === 'undefined' && hasShow ? defaultActions : actions;
44-
if ((!children && !render) || (!record && isPending && emptyWhileLoading)) {
44+
45+
if (!record && isPending && emptyWhileLoading) {
4546
return null;
4647
}
4748

packages/ra-ui-materialui/src/list/List.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ export const List = <RecordType extends RaRecord = any>(
8080
name: PREFIX,
8181
});
8282

83+
if (!props.render && !props.children) {
84+
throw new Error(
85+
'<List> requires either a `render` prop or `children` prop'
86+
);
87+
}
88+
8389
return (
8490
<ListBase<RecordType>
8591
debounce={debounce}

0 commit comments

Comments
 (0)