Skip to content

Commit

Permalink
Merge pull request #3262 from marmelab/replace_injected_elements_by_i…
Browse files Browse the repository at this point in the history
…njected_components

[RFR] Replace injected elements by injected components
  • Loading branch information
fzaninotto authored May 23, 2019
2 parents 078d026 + 5388e1c commit 05699c4
Show file tree
Hide file tree
Showing 100 changed files with 733 additions and 834 deletions.
70 changes: 70 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,80 @@ 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:

* `AppBarMobile` (use `AppBar` instead, which is responsive)
* `Header` (use `Title` instead)
* `ViewTitle` (use `Title` instead)
* `RecordTitle` (use `TitleForRecord` instead)
* `TitleDeprecated` (use `Title` instead)
5 changes: 4 additions & 1 deletion cypress/cypress.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"supportFile": "support/index.js",
"videosFolder": "videos",
"viewportWidth": 1280,
"viewportHeight": 720
"viewportHeight": 720,
"blacklistHosts": [
"source.unsplash.com"
]
}
2 changes: 1 addition & 1 deletion docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ const Menu = ({ resources, onMenuClick, logout }) => (
<MenuItemLink
to="/custom-route"
primaryText="Miscellaneous"
leftIcon={<LabelIcon />}
leftIcon={LabelIcon}
onClick={onMenuClick} />
<Responsive
small={logout}
Expand Down
6 changes: 3 additions & 3 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={<UserCreateToolbar permissions={permissions} />}
toolbar={props => <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 Expand Up @@ -173,7 +173,7 @@ const UserFilter = ({ permissions, ...props }) =>
export const UserList = ({ permissions, ...props }) =>
<List
{...props}
filters={<UserFilter permissions={permissions} />}
filters={props => <UserFilter permissions={permissions} {...props} />}
sort={{ field: 'name', order: 'ASC' }}
>
<Responsive
Expand Down
20 changes: 10 additions & 10 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 @@ -107,15 +107,15 @@ 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 element using the `actions` prop:
You can replace the list of default actions by your own component 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={<UserCreateToolbar permissions={permissions} />}
toolbar={props => <UserCreateToolbar permissions={permissions} {...props} />}
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
4 changes: 2 additions & 2 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 @@ -619,7 +619,7 @@ And if you want to allow users to paginate the list, pass a `<Pagination>` compo
```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

0 comments on commit 05699c4

Please sign in to comment.