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

Remove cloneElement in list actions and bulk actions #9707

Merged
merged 3 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/CreateDialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Then, add the `<CreateDialog>` component as a sibling to a `<List>` component.
```jsx
import {
List,
ListActions,
Datagrid,
SimpleForm,
TextInput,
Expand All @@ -42,7 +43,7 @@ import { CreateDialog } from '@react-admin/ra-form-layout';

const CustomerList = () => (
<>
<List hasCreate>
<List actions={<ListActions hasCreate />}>
<Datagrid>
...
</Datagrid>
Expand All @@ -58,7 +59,7 @@ const CustomerList = () => (
);
```

**Tip**: In the example above, notice the `<List hasCreate>` prop. It is necessary in order to display the "Create" button, because react-admin has no way of knowing that creation form for the "customer" resource exists.
**Tip**: In the example above, notice the `<List actions>` prop. It is necessary in order to display the "Create" button, because react-admin has no way of knowing that creation form for the "customer" resource exists.

In the related `<Resource>`, you don't need to declare a `create` component as the creation UI is part of the `list` component:

Expand Down
3 changes: 2 additions & 1 deletion docs/Datagrid.md
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,7 @@ The separation between list pages and edit pages is not always relevant. Sometim
```tsx
import {
List,
ListActions,
TextField,
TextInput,
DateField,
Expand All @@ -982,7 +983,7 @@ const professionChoices = [
];

const ArtistList = () => (
<List hasCreate empty={false}>
<List actions={<ListActions hasCreate />} empty={false}>
<EditableDatagrid
mutationMode="undoable"
createForm={<ArtistForm />}
Expand Down
25 changes: 15 additions & 10 deletions docs/EditableDatagrid.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ yarn add @react-admin/ra-editable-datagrid
Then, replace `<Datagrid>` with `<EditableDatagrid>` in a react-admin `<List>`, `<ReferenceManyField>`, or any other component that creates a `ListContext`. In addition, pass a form component to be displayed when the user switches to edit or create mode.

```tsx
import { List, TextField, TextInput, DateField, DateInput, SelectField, SelectInput, required } from 'react-admin';
import { List, ListActions,TextField, TextInput, DateField, DateInput, SelectField, SelectInput, required } from 'react-admin';
import { EditableDatagrid, RowForm } from '@react-admin/ra-editable-datagrid';

export const ArtistList = () => (
<List hasCreate empty={false}>
<List actions={<ListActions hasCreate />} empty={false}>
<EditableDatagrid
createForm={<ArtistForm />}
editForm={<ArtistForm />}
Expand Down Expand Up @@ -136,7 +136,7 @@ The component displayed as the first row when a user clicks on the Create button

```tsx
export const ArtistList = () => (
<List hasCreate empty={false}>
<List actions={<ListActions hasCreate />} empty={false}>
<EditableDatagrid
editForm={<ArtistForm />}
createForm={<ArtistForm />}
Expand All @@ -161,7 +161,7 @@ const ArtistForm = () => (

**Tip**: It's a good idea to reuse the same form component for `createForm` and `editForm`, as in the example above.

Since the creation form is embedded in the List view, you shouldn't set the `<Resource create>` prop. But react-admin's `<List>` only displays a Create button if the current `Resource` has a `create` page. That's why you must force the [`<List hasCreate>`](./List.md#hascreate) prop to `true`, as in the example above, to have the Create button show up with `<EditableDatagrid>`.
Since the creation form is embedded in the List view, you shouldn't set the `<Resource create>` prop. But react-admin's `<List>` only displays a Create button if the current `Resource` has a `create` page. That's why you must force the [`<List actions>`](./List.md#actions) value, as in the example above, to have the Create button show up with `<EditableDatagrid>`.

Also, when the list is empty, the `<List>` component normally doesn't render its children (it renders an `empty` component instead). To bypass this system and see the empty editable datagrid with a create button instead, you need to force the `<List empty={false}>` prop, as in the example above.

Expand Down Expand Up @@ -233,11 +233,11 @@ You can disable the delete button by setting the `noDelete` prop to `true`:
`<RowForm>` renders a form in a table row, with one table cell per child. It is designed to be used as [`editForm`](#editform) and [`createForm`](#createform) element.

```tsx
import { List, TextField, TextInput } from 'react-admin';
import { List, ListActions, TextField, TextInput } from 'react-admin';
import { EditableDatagrid, RowForm } from '@react-admin/ra-editable-datagrid';

export const ArtistList = () => (
<List hasCreate empty={false}>
<List actions={<ListActions hasCreate />} empty={false}>
<EditableDatagrid
createForm={<ArtistForm />}
editForm={<ArtistForm />}
Expand Down Expand Up @@ -299,7 +299,7 @@ For instance, the following example displays a custom message when the list is e
```tsx
import React from 'react';
import { Typography, Box } from '@mui/material';
import { CreateButton, List } from 'react-admin';
import { CreateButton, List, ListActions } from 'react-admin';
import {
EditableDatagrid,
useEditableDatagridContext,
Expand All @@ -320,7 +320,7 @@ const MyCreateButton = () => {
};

export const BookList = () => (
<List hasCreate empty={false}>
<List actions={<ListActions hasCreate />} empty={false}>
<EditableDatagrid empty={<MyCreateButton />}>
{/*...*/}
</EditableDatagrid>
Expand Down Expand Up @@ -512,6 +512,7 @@ import {
SelectField,
required,
List,
ListActions,
} from 'react-admin';
import {
EditableDatagrid,
Expand Down Expand Up @@ -541,7 +542,11 @@ const ArtistForm = ({ meta }) => (
const ArtistListWithMeta = () => {
const meta = { foo: 'bar' };
return (
<List hasCreate sort={{ field: 'id', order: 'DESC' }} empty={false}>
<List
actions={<ListActions hasCreate />}
sort={{ field: 'id', order: 'DESC' }}
empty={false}
>
<EditableDatagrid
createForm={<ArtistForm meta={meta} />}
editForm={<ArtistForm meta={meta} />}
Expand Down Expand Up @@ -605,7 +610,7 @@ const ArtistForm = ({ meta }) => (
);

const ArtistList = () => (
<List hasCreate empty={false}>
<List actions={<ListActions hasCreate />} empty={false}>
- <EditableDatagrid
+ <EditableDatagridConfigurable
mutationMode="undoable"
Expand Down
1 change: 0 additions & 1 deletion docs/InfiniteList.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ The props are the same as [the `<List>` component](./List.md):
| `filters` | Optional | `ReactElement` | - | The filters to display in the toolbar. |
| `filter` | Optional | `object` | - | The permanent filter values. |
| `filter DefaultValues` | Optional | `object` | - | The default filter values. |
| `hasCreate` | Optional | `boolean` | `false` | Set to `true` to show the create button. |
| `pagination` | Optional | `ReactElement` | `<Infinite Pagination>` | The pagination component to use. |
| `perPage` | Optional | `number` | `10` | The number of records to fetch per page. |
| `queryOptions` | Optional | `object` | - | The options to pass to the `useQuery` hook. |
Expand Down
31 changes: 16 additions & 15 deletions docs/List.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ You can find more advanced examples of `<List>` usage in the [demos](./Demos.md)
| `filters` | Optional | `ReactElement` | - | The filters to display in the toolbar. |
| `filter` | Optional | `object` | - | The permanent filter values. |
| `filter DefaultValues` | Optional | `object` | - | The default filter values. |
| `hasCreate` | Optional | `boolean` | `false` | Set to `true` to show the create button. |
| `pagination` | Optional | `ReactElement` | `<Pagination>` | The pagination component to use. |
| `perPage` | Optional | `number` | `10` | The number of records to fetch per page. |
| `queryOptions` | Optional | `object` | - | The options to pass to the `useQuery` hook. |
Expand All @@ -84,12 +83,26 @@ Additional props are passed down to the root component (a MUI `<Card>` by defaul
By default, the `<List>` view displays a toolbar on top of the list. It contains:

- A `<FilterButton>` to display the filter form if you set [the `filters` prop](#filters-filter-inputs)
- A `<CreateButton>` if the resource has a creation view, or if you set [the `hasCreate` prop](#hascreate)
- A `<CreateButton>` if the resource has a creation view
- An `<ExportButton>`

![Actions Toolbar](./img/actions-toolbar.png)

You can replace this toolbar by your own using the `actions` prop. For instance, to add a [`<SelectColumnsButton>`](./SelectColumnsButton.md) to let the user choose which columns to display in the list:
The `actions` prop allows you to replace the default toolbar by your own.

For instance, you can force the toolbar to display a Create button, even if the resource has no creation view, by passing a custom `<ListActions>` component:

```jsx
import { List, ListActions } from 'react-admin';

export const PostList = () => (
<List actions={<ListActions hasCreate />}>
...
</List>
);
```

You can also add custom actions, e.g. a [`<SelectColumnsButton>`](./SelectColumnsButton.md) to let the user choose which columns to display in the list:

```jsx
import {
Expand Down Expand Up @@ -718,18 +731,6 @@ export const PostList = () => (
const filterSentToDataProvider = { ...filterDefaultValues, ...filterChosenByUser, ...filter };
```

## `hasCreate`

The List page shows a Create button if the resource has a create view, or if the `hasCreate` prop is set to true. Using this prop lets you force the display of the create button, or hide it.

```jsx
export const PostList = () => (
<List hasCreate={false}>
...
</List>
);
```

## `pagination`

By default, the `<List>` view displays a set of pagination controls at the bottom of the list.
Expand Down
67 changes: 67 additions & 0 deletions docs/Upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,22 @@ The following deprecated hooks have been removed

- `usePermissionsOptimized`. Use `usePermissions` instead.

## `<List hasCreate>` Is No Longer Supported

To force a List view to display a Create button even though the corresponding resource doesn't have a `create` component, pass a custom actions component to the List component:

```diff
-import { List } from 'react-admin';
+import { List, ListActions } from 'react-admin';

const PostList = () => (
- <List hasCreate>
+ <List actions={<ListActions hasCreate />}>
...
</List>
);
```

## `<Datagrid rowClick>` is no longer `false` by default

`<Datagrid>` will now make the rows clickable as soon as a Show or Edit view is declared on the resource (using the [resource definition](https://marmelab.com/react-admin/Resource.html)).
Expand All @@ -289,6 +305,57 @@ If you previously relied on the fact that the rows were not clickable by default
</Datagrid>
```

## Updates to `bulkActionButtons` Syntax

The `bulkActionButtons` prop has been moved from the `<List>` component to the `<Datagrid>` component.

```diff
const PostList = () => (
- <List bulkActionButtons={<BulkActionButtons />}>
+ <List>
- <Datagrid>
+ <Datagrid bulkActionButtons={<BulkActionButtons />}>
...
</Datagrid>
</List>
);
```

Besides, the buttons passed as `bulkActionButtons` no longer receive any prop. If you need the current filter values or the selected ids, you'll have to use the `useListContext` hook:

```diff
-const BulkResetViewsButton = ({ resource, selectedIds }) => {
+const BulkResetViewsButton = () => {
+ const { resource, selectedIds } = useListContext();
const notify = useNotify();
const unselectAll = useUnselectAll(resource);
const [updateMany, { isPending }] = useUpdateMany();

const handleClick = () => {
updateMany(
resource,
{ ids: selectedIds, data: { views: 0 } },
{
onSuccess: () => {
notify('Views reset');
unselectAll();
},
onError: () => notify('Views not reset', { type: 'error' }),
}
);
}
return (
<Button
label="Reset views"
disabled={isPending}
onClick={() => updateMany()}
>
<VisibilityOff />
</Button>
);
};
```

## Dark Theme Is Available By Default

In addition to the light theme, React-admin v5 includes a [dark theme](https://marmelab.com/react-admin/AppTheme.html#light-and-dark-themes), renders a theme switcher in the app bar, and chooses the default theme based on the user OS preferences.
Expand Down
10 changes: 6 additions & 4 deletions docs/useDeleteMany.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ So, should you pass the parameters when calling the hook, or when executing the

```jsx
// set params when calling the hook
import { useDeleteMany } from 'react-admin';
import { useListContext, useDeleteMany } from 'react-admin';

const BulkDeletePostsButton = ({ selectedIds }) => {
const BulkDeletePostsButton = () => {
const { selectedIds } = useListContext();
const [deleteMany, { isPending, error }] = useDeleteMany(
'posts',
{ ids: selectedIds }
Expand All @@ -48,9 +49,10 @@ const BulkDeletePostsButton = ({ selectedIds }) => {
};

// set params when calling the deleteMany callback
import { useDeleteMany } from 'react-admin';
import { useListContext, useDeleteMany } from 'react-admin';

const BulkDeletePostsButton = ({ selectedIds }) => {
const BulkDeletePostsButton = () => {
const { selectedIds } = useListContext();
const [deleteMany, { isPending, error }] = useDeleteMany();
const handleClick = () => {
deleteMany(
Expand Down
31 changes: 11 additions & 20 deletions examples/simple/src/posts/PostList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';
import { Fragment, memo } from 'react';
import BookIcon from '@mui/icons-material/Book';
import { Box, Chip, useMediaQuery } from '@mui/material';
import { Theme, styled } from '@mui/material/styles';
Expand Down Expand Up @@ -67,7 +66,7 @@ const exporter = posts => {
return jsonExport(data, (err, csv) => downloadCSV(csv, 'posts'));
};

const PostListMobileActions = () => (
const postListMobileActions = (
<TopToolbar>
<FilterButton />
<CreateButton />
Expand All @@ -80,7 +79,7 @@ const PostListMobile = () => (
filters={postFilter}
sort={{ field: 'published_at', order: 'DESC' }}
exporter={exporter}
actions={<PostListMobileActions />}
actions={postListMobileActions}
>
<SimpleList
primaryText={record => record.title}
Expand Down Expand Up @@ -110,23 +109,15 @@ const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
'& .publishedAt': { fontStyle: 'italic' },
}));

const PostListBulkActions = memo(
({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
children,
...props
}: {
children?: React.ReactNode;
}) => (
<Fragment>
<ResetViewsButton {...props} />
<BulkDeleteButton {...props} />
<BulkExportButton {...props} />
</Fragment>
)
const postListBulkActions = (
<>
<ResetViewsButton />
<BulkDeleteButton />
<BulkExportButton />
</>
);

const PostListActions = () => (
const postListActions = (
<TopToolbar>
<SelectColumnsButton />
<FilterButton />
Expand Down Expand Up @@ -158,10 +149,10 @@ const PostListDesktop = () => (
filters={postFilter}
sort={{ field: 'published_at', order: 'DESC' }}
exporter={exporter}
actions={<PostListActions />}
actions={postListActions}
>
<StyledDatagrid
bulkActionButtons={<PostListBulkActions />}
bulkActionButtons={postListBulkActions}
rowClick={rowClick}
expand={PostPanel}
omit={['average_note']}
Expand Down
11 changes: 9 additions & 2 deletions examples/simple/src/posts/ResetViewsButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import { useUpdateMany, useNotify, useUnselectAll, Button } from 'react-admin';
import {
useUpdateMany,
useNotify,
useUnselectAll,
Button,
useListContext,
} from 'react-admin';

const ResetViewsButton = ({ resource, selectedIds }) => {
const ResetViewsButton = () => {
const { resource, selectedIds } = useListContext();
const notify = useNotify();
const unselectAll = useUnselectAll(resource);
const [updateMany, { isPending }] = useUpdateMany(
Expand Down
Loading
Loading