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] Replace component injection with element injection #3312

Merged
merged 16 commits into from
Jun 6, 2019
68 changes: 0 additions & 68 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,74 +154,6 @@ const App = () => (
);
```

## Injected Elements Replaced By Injected Components

Every time that you used an *element* as a prop in a react-admin component, you must now use a *component*. This basically means removing the enclosing angle brackets in props:

```diff
const PostEdit = (props) => (
- <Edit title={<PostTitle />} actions={<EditActions />} {...props}>
+ <Edit title={PostTitle} actions={EditActions} {...props}>
```

If an element prop depended on higher props, you must use an inline component instead:

```diff
const PostEdit = ({ permissions, ...props }) => (
- <Edit actions={<EditActions permissions={permissions} />} {...props}>
+ <Edit actions={actionProps => <EditActions permissions={permissions} {...actionProps} />} {...props}>
```

We are aware that this will require many changes in existing codebases. Fortunately, it can be automated for the most part. You might find the following regular expressions useful for migrating.

The first, `{<(.+)\/>}`, searches for all element injections, for instance:

* `{<EditActions />}`
* `{<EditActions permissions={permissions} />}`

You can then use `{props => <$1{...props} />}` as the replacement pattern. The result will be:

* `{props => <EditActions {...props} />}`
* `{props => <EditActions permissions={permissions} {...props} />}`

However, in most cases you do not need an inline component, so you might want to use another replacement pattern afterwards: `{props => <(\w+) {\.\.\.props} \/>}`. It searches for simple component injections without extra props, such as `{props => <EditActions {...props} />}`, but will not match more complex cases like `{props => <EditActions permissions={permissions} {...props} />}`. You can then use `{$1}` as the replacement pattern, which will produce `{EditActions}`.

For reference, you can read the [RFC](https://github.com/marmelab/react-admin/issues/3246) about this change to understand the rationale.

## Remove optionText={function} for Input Components

Many Input components for selecting items in a list expose an `optionText` prop. This prop used to accept 3 types of values: a field name, a function, and a React element. Here is an example with `AutocompleteInput` using a function as `optionText` prop:

```jsx
const choices = [
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
<AutocompleteInput source="author_id" choices={choices} optionText={optionRenderer} />
```

However, as these Input components no longer accept element as props (but components), there is no way react-admin can distinguish simple functions from elements. Indeed, they might be functions accepting props.

So `optionText` still works, but no longer accepts a simple function - only a component.

The migration shouldn't be too hard though. To turn a function into a component, wrap it inside Fragment tags:

```diff
-const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
-<AutocompleteInput source="author_id" choices={choices} optionText={optionRenderer} />
+const Option = ({ record }) => <>{record.first_name} {record.last_name}</>;
+<AutocompleteInput source="author_id" choices={choices} optionText={Option} />
```

This change concerns the following components:

* `CheckboxGroupInput`
* `SelectArrayInput`
* `SelectInput`
* `SelectField`
* `RadioButtonGroupInput`

## Deprecated components were removed

Components deprecated in 2.X have been removed in 3.x. This includes:
Expand Down
4 changes: 2 additions & 2 deletions docs/Authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ const UserCreateToolbar = ({ permissions, ...props }) =>
export const UserCreate = ({ permissions, ...props }) =>
<Create {...props}>
<SimpleForm
toolbar={props => <UserCreateToolbar permissions={permissions} {...props} />}
toolbar={<UserCreateToolbar permissions={permissions} {...props} />}
defaultValue={{ role: 'user' }}
>
<TextInput source="name" validate={[required()]} />
Expand All @@ -140,7 +140,7 @@ This also works inside an `Edition` view with a `TabbedForm`, and you can hide a
{% raw %}
```jsx
export const UserEdit = ({ permissions, ...props }) =>
<Edit title={UserTitle} {...props}>
<Edit title={<UserTitle />} {...props}>
<TabbedForm defaultValue={{ role: 'user' }}>
<FormTab label="user.form.summary">
{permissions === 'admin' && <DisabledInput source="id" />}
Expand Down
22 changes: 11 additions & 11 deletions docs/CreateEdit.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const PostCreate = (props) => (
);

export const PostEdit = (props) => (
<Edit title={PostTitle} {...props}>
<Edit title={<PostTitle />} {...props}>
<SimpleForm>
<DisabledInput label="Id" source="id" />
<TextInput source="title" validate={required()} />
Expand Down Expand Up @@ -100,22 +100,22 @@ export const PostEdit = (props) => (
);
```

More interestingly, you can pass a component as `title`. React-admin clones this component and, in the `<EditView>`, injects the current `record`. This allows to customize the title according to the current record:
More interestingly, you can pass an element as `title`. React-admin clones this element and, in the `<EditView>`, injects the current `record`. This allows to customize the title according to the current record:

```jsx
const PostTitle = ({ record }) => {
return <span>Post {record ? `"${record.title}"` : ''}</span>;
};
export const PostEdit = (props) => (
<Edit title={PostTitle} {...props}>
<Edit title={<PostTitle />} {...props}>
...
</Edit>
);
```

### Actions

You can replace the list of default actions by your own component using the `actions` prop:
You can replace the list of default actions by your own element using the `actions` prop:

```jsx
import Button from '@material-ui/core/Button';
Expand All @@ -130,7 +130,7 @@ const PostEditActions = ({ basePath, data, resource }) => (
);

export const PostEdit = (props) => (
<Edit actions={PostEditActions} {...props}>
<Edit actions={<PostEditActions />} {...props}>
...
</Edit>
);
Expand All @@ -152,7 +152,7 @@ const Aside = () => (
);

const PostEdit = props => (
<Edit aside={Aside} {...props}>
<Edit aside={<Aside />} {...props}>
...
</Edit>
```
Expand Down Expand Up @@ -218,7 +218,7 @@ const CustomToolbar = withStyles(toolbarStyles)(props => (

const PostEdit = props => (
<Edit {...props}>
<SimpleForm toolbar={CustomToolbar}>
<SimpleForm toolbar={<CustomToolbar />}>
...
</SimpleForm>
</Edit>
Expand Down Expand Up @@ -718,7 +718,7 @@ const PostCreateToolbar = props => (

export const PostCreate = (props) => (
<Create {...props}>
<SimpleForm toolbar={PostCreateToolbar} redirect="show">
<SimpleForm toolbar={<PostCreateToolbar />} redirect="show">
...
</SimpleForm>
</Create>
Expand All @@ -738,7 +738,7 @@ const PostEditToolbar = props => (

export const PostEdit = (props) => (
<Edit {...props}>
<SimpleForm toolbar={PostEditToolbar}>
<SimpleForm toolbar={<PostEditToolbar />}>
...
</SimpleForm>
</Edit>
Expand Down Expand Up @@ -811,7 +811,7 @@ const UserCreateToolbar = ({ permissions, ...props }) =>
export const UserCreate = ({ permissions, ...props }) =>
<Create {...props}>
<SimpleForm
toolbar={props => <UserCreateToolbar permissions={permissions} {...props} />}
toolbar={<UserCreateToolbar permissions={permissions} />}
defaultValue={{ role: 'user' }}
>
<TextInput source="name" validate={[required()]} />
Expand All @@ -829,7 +829,7 @@ This also works inside an `Edition` view with a `TabbedForm`, and you can hide a
{% raw %}
```jsx
export const UserEdit = ({ permissions, ...props }) =>
<Edit title={UserTitle} {...props}>
<Edit title={<UserTitle />} {...props}>
<TabbedForm defaultValue={{ role: 'user' }}>
<FormTab label="user.form.summary">
{permissions === 'admin' && <DisabledInput source="id" />}
Expand Down
6 changes: 3 additions & 3 deletions docs/Fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ const choices = [
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const FullNameField = ({ record }) => <Chip>{record.first_name} {record.last_name}</Chip>;
<SelectField source="author_id" choices={choices} optionText={FullNameField}/>
<SelectField source="author_id" choices={choices} optionText={<FullNameField />}/>
```

The current choice is translated by default, so you can use translation identifiers as choices:
Expand Down Expand Up @@ -614,12 +614,12 @@ By default, react-admin restricts the possible values to 25 and displays no pagi
</ReferenceManyField>
```

And if you want to allow users to paginate the list, pass a `<Pagination>` component as the `pagination` prop:
And if you want to allow users to paginate the list, pass a `<Pagination>` element as the `pagination` prop:

```jsx
import { Pagination } from 'react-admin';

<ReferenceManyField pagination={Pagination} reference="comments" target="post_id">
<ReferenceManyField pagination={<Pagination />} reference="comments" target="post_id">
...
</ReferenceManyField>
```
Expand Down
8 changes: 4 additions & 4 deletions docs/Inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import React from 'react';
import { Edit, DisabledInput, LongTextInput, ReferenceInput, SelectInput, SimpleForm, TextInput } from 'react-admin';

export const PostEdit = (props) => (
<Edit title={PostTitle} {...props}>
<Edit title={<PostTitle />} {...props}>
<SimpleForm>
<DisabledInput source="id" />
<ReferenceInput label="User" source="userId" reference="users">
Expand Down Expand Up @@ -433,7 +433,7 @@ const choices = [
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const FullNameField = ({ record }) => <span>{record.first_name} {record.last_name}</span>;
<CheckboxGroupInput source="gender" choices={choices} optionText={FullNameField}/>
<CheckboxGroupInput source="gender" choices={choices} optionText={<FullNameField />}/>
```

The choices are translated by default, so you can use translation identifiers as choices:
Expand Down Expand Up @@ -685,7 +685,7 @@ const choices = [
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const FullNameField = ({ record }) => <span>{record.first_name} {record.last_name}</span>;
<RadioButtonGroupInput source="gender" choices={choices} optionText={FullNameField}/>
<RadioButtonGroupInput source="gender" choices={choices} optionText={<FullNameField />}/>
```

The choices are translated by default, so you can use translation identifiers as choices:
Expand Down Expand Up @@ -1001,7 +1001,7 @@ const choices = [
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const FullNameField = ({ record }) => <span>{record.first_name} {record.last_name}</span>;
<SelectInput source="gender" choices={choices} optionText={FullNameField}/>
<SelectInput source="gender" choices={choices} optionText={<FullNameField />}/>
```

Enabling the `allowEmpty` props adds an empty choice (with a default `null` value, which you can overwrite with the `emptyValue` prop) on top of the options, and makes the value nullable:
Expand Down
Loading