Skip to content

Commit 61188e9

Browse files
authored
Merge pull request #8711 from marmelab/doc-confirm
[Doc] Improve `<Confirm>` documentation
2 parents 38b7172 + 812be15 commit 61188e9

File tree

6 files changed

+197
-19
lines changed

6 files changed

+197
-19
lines changed

docs/Confirm.md

+58-16
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,47 @@ title: "The Confirm Component"
77

88
`<Confirm>` leverages MUI's [`<Dialog>` component](https://mui.com/components/dialogs) to implement a confirmation popup.
99

10-
![Confirm dialog](./img/confirm-dialog.png)
10+
![Confirm dialog](./img/confirm.webp)
11+
12+
## Usage
13+
14+
To ask a confirmation to the user before performing an action, have the action button open a `<Confirm>`.
15+
16+
For instance, here is how to build a delete button that removes the record after asking for confirmation:
1117

1218
```jsx
1319
import { useState } from 'react';
1420
import {
1521
Button,
1622
Confirm,
17-
useListContext,
18-
useUpdateMany,
23+
useRecordContext,
24+
useDelete,
1925
} from 'react-admin';
2026

21-
const CustomUpdatePostsButton = () => {
22-
const { selectedIds } = useListContext();
27+
const BulkResetViewsButton = () => {
28+
const record = useRecordContext();
2329
const [open, setOpen] = useState(false);
2430

25-
const [updateMany, { isLoading }] = useUpdateMany(
31+
const [remove, { isLoading }] = useDelete(
2632
'posts',
27-
{ ids: selectedIds, data: { views: 0 } }
33+
{ id: record && record.id }
2834
);
2935

3036
const handleClick = () => setOpen(true);
3137
const handleDialogClose = () => setOpen(false);
3238
const handleConfirm = () => {
33-
updateMany();
39+
remove();
3440
setOpen(false);
3541
};
3642

3743
return (
3844
<>
39-
<Button label="Update Posts" onClick={handleClick} />
45+
<Button label="Delete" onClick={handleClick} />
4046
<Confirm
4147
isOpen={open}
4248
loading={isLoading}
43-
title="Update View Count"
44-
content="Are you sure you want to update these posts?"
49+
title={`Delete post #${record && record.id}`}
50+
content="Are you sure you want to delete this item?"
4551
onConfirm={handleConfirm}
4652
onClose={handleDialogClose}
4753
/>
@@ -50,20 +56,21 @@ const CustomUpdatePostsButton = () => {
5056
};
5157
```
5258

59+
## Props
60+
5361
| Prop | Required | Type | Default | Description |
5462
|--------------------|----------|----------------------------------|-----------------------|--------------------------------------------------------------------|
55-
| `className` | Optional | `string` | - | Class name to customize the look and feel of the dialog itself |
63+
| `title` | Required | `string` | - | Title of the dialog |
64+
| `content` | Required | `ReactNode` | - | Body of the dialog |
65+
| `onClose` | Required | `MouseEventHandler` | - | onClick event handler of the cancel button |
66+
| `onConfirm` | Required | `MouseEventHandler` | - | onClick event handler of the confirm button |
5667
| `isOpen` | Optional | `boolean` | `false` | `true` to show the dialog, `false` to hide it |
5768
| `loading` | Optional | `boolean` | `false` | Boolean to be applied to the `disabled` prop of the action buttons |
58-
| `content` | Required | `ReactNode` | - | Body of the dialog |
5969
| `cancel` | Optional | `string` | 'ra.action.cancel' | Label of the cancel button |
6070
| `confirm` | Optional | `string` | 'ra.action.confirm' | Label of the confirm button |
6171
| `confirmColor` | Optional | `string` | 'primary' | Color of the confirm button |
6272
| `ConfirmIcon` | Optional | `ReactElement` | `<CheckCircle/>` | Icon element of the confirm button |
6373
| `CancelIcon` | Optional | `ReactElement` | `<ErrorOutlineIcon/>` | Icon element of the cancel button |
64-
| `onClose` | Required | `MouseEventHandler` | - | onClick event handler of the cancel button |
65-
| `onConfirm` | Required | `MouseEventHandler` | - | onClick event handler of the confirm button |
66-
| `title` | Required | `string` | - | Title of the dialog |
6774
| `translateOptions` | Optional | `{ id?: string, name?: string }` | {} | Custom id and name to be used in the dialog title |
6875
| `sx` | Optional | `SxProps` | '' | MUI shortcut for defining custom styles with access to the theme |
6976

@@ -79,3 +86,38 @@ The `<Confirm>` component accepts the usual `className` prop. You can also overr
7986
| `& .RaConfirm-confirmWarning` | Applied to the confirm button when `confirmColor` is `warning` |
8087

8188
To override the style of all instances of `<Confirm>` using the [MUI style overrides](https://mui.com/customization/globals/#css), use the `RaConfirm` key.
89+
90+
## Delete With Confirmation
91+
92+
React-admin's `<DeleteButton>` lets user delete the current record [in an optimistic way](./Features.md#optimistic-updates-and-undo): after clicking the button, users see a notification for the deletion with an "undo" link to cancel the deletion.
93+
94+
Alternately, you can force the user to confirm the deletion by using `<DeleteButton mutationMode="pessimistic">`. Under the hood, this leverages the `<Confirm>` component to ask for confirmation before deleting the record.
95+
96+
```jsx
97+
import { List, Datagrid, TextField, DeleteButton } from 'react-admin';
98+
99+
const PostList = () => (
100+
<List>
101+
<Datagrid>
102+
<TextField source="id" />
103+
<TextField source="title" />
104+
<DeleteButton mutationMode="pessimistic" />
105+
</Datagrid>
106+
</List>
107+
);
108+
```
109+
110+
The same goes for deleting multiple records in a [bulk action](./Datagrid.md#bulkactionbuttons): use `<BulkDeleteButton mutationMode="pessimistic">` to ask a confirmation before the deletion.
111+
112+
```jsx
113+
import { List, Datagrid, TextField, BulkDeleteButton } from 'react-admin';
114+
115+
const PostList = () => (
116+
<List>
117+
<Datagrid PostBulkActionButtons={<BulkDeleteButton mutationMode="pessimistic" />}>
118+
<TextField source="id" />
119+
<TextField source="title" />
120+
</Datagrid>
121+
</List>
122+
);
123+
```

docs/Datagrid.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ const ResetViewsButton = () => (
193193
export default ResetViewsButton;
194194
```
195195

196-
You can also implement the same `<ResetViewsButton>` behind a confirmation dialog by using the [`mutationMode`](./Edit.md#mutationmode) prop:
196+
You can also implement the same `<ResetViewsButton>` behind a [confirmation dialog](./Confirm.md) by using the [`mutationMode`](./Edit.md#mutationmode) prop:
197197

198198
```diff
199199
// in ./ResetViewsButton.js

docs/Edit.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ const PostEdit = () => (
230230

231231
**Tip**: When using any other mode than `undoable`, the `<DeleteButton>` displays a confirmation dialog before calling the dataProvider.
232232

233-
**Tip**: If you want a confirmation dialog for the Delete button but don't mind undoable Edits, then pass a [custom toolbar](./SimpleForm.md#toolbar) to the form, as follows:
233+
**Tip**: If you want a [confirmation dialog](./Confirm.md) for the Delete button but don't mind undoable Edits, then pass a [custom toolbar](./SimpleForm.md#toolbar) to the form, as follows:
234234

235235
{% raw %}
236236
```jsx

docs/img/confirm.webp

31.1 KB
Binary file not shown.

docs/navigation.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@
224224
<li {% if page.path == 'HorizontalMenu.md' %} class="active" {% endif %}><a class="nav-link" href="./HorizontalMenu.html"><code>&lt;HorizontalMenu&gt;</code><img class="premium" src="./img/premium.svg" /></a></li>
225225
<li {% if page.path == 'Breadcrumb.md' %} class="active" {% endif %}><a class="nav-link" href="./Breadcrumb.html"><code>&lt;Breadcrumb&gt;</code><img class="premium" src="./img/premium.svg" /></a></li>
226226
<li {% if page.path == 'Search.md' %} class="active" {% endif %}><a class="nav-link" href="./Search.html"><code>&lt;Search&gt;</code><img class="premium" src="./img/premium.svg" /></a></li>
227+
<li {% if page.path == 'Confirm.md' %} class="active" {% endif %}><a class="nav-link" href="./Confirm.html"><code>&lt;Confirm&gt;</code></a></li>
227228
<li {% if page.path == 'Buttons.md' %} class="active" {% endif %}><a class="nav-link" href="./Buttons.html">Buttons</a></li>
228-
<li {% if page.path == 'Confirm.md' %} class="active" {% endif %}><a class="nav-link" href="./Confirm.html">Confirm</a></li>
229229
</ul>
230230

231231
<ul><div>Realtime</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import * as React from 'react';
2+
import polyglotI18nProvider from 'ra-i18n-polyglot';
3+
import englishMessages from 'ra-language-english';
4+
import frenchMessages from 'ra-language-french';
5+
import { Resource } from 'ra-core';
6+
import fakeRestDataProvider from 'ra-data-fakerest';
7+
import { createMemoryHistory } from 'history';
8+
9+
import { DeleteWithConfirmButton } from './DeleteWithConfirmButton';
10+
import { AdminContext } from '../AdminContext';
11+
import { AdminUI } from '../AdminUI';
12+
import { List, Datagrid } from '../list';
13+
import { TextField } from '../field';
14+
15+
export default { title: 'ra-ui-materialui/button/DeleteWithConfirmButton' };
16+
17+
const i18nProvider = polyglotI18nProvider(
18+
locale =>
19+
locale === 'fr'
20+
? {
21+
...frenchMessages,
22+
resources: {
23+
books: {
24+
name: 'Livre |||| Livres',
25+
fields: {
26+
id: 'Id',
27+
title: 'Titre',
28+
author: 'Auteur',
29+
year: 'Année',
30+
},
31+
},
32+
},
33+
}
34+
: englishMessages,
35+
'en' // Default locale
36+
);
37+
38+
const dataProvider = fakeRestDataProvider({
39+
books: [
40+
{
41+
id: 1,
42+
title: 'War and Peace',
43+
author: 'Leo Tolstoy',
44+
year: 1869,
45+
},
46+
{
47+
id: 2,
48+
title: 'Pride and Predjudice',
49+
author: 'Jane Austen',
50+
year: 1813,
51+
},
52+
{
53+
id: 3,
54+
title: 'The Picture of Dorian Gray',
55+
author: 'Oscar Wilde',
56+
year: 1890,
57+
},
58+
{
59+
id: 4,
60+
title: 'Le Petit Prince',
61+
author: 'Antoine de Saint-Exupéry',
62+
year: 1943,
63+
},
64+
{
65+
id: 5,
66+
title: "Alice's Adventures in Wonderland",
67+
author: 'Lewis Carroll',
68+
year: 1865,
69+
},
70+
{
71+
id: 6,
72+
title: 'Madame Bovary',
73+
author: 'Gustave Flaubert',
74+
year: 1856,
75+
},
76+
{
77+
id: 7,
78+
title: 'The Lord of the Rings',
79+
author: 'J. R. R. Tolkien',
80+
year: 1954,
81+
},
82+
{
83+
id: 8,
84+
title: "Harry Potter and the Philosopher's Stone",
85+
author: 'J. K. Rowling',
86+
year: 1997,
87+
},
88+
{
89+
id: 9,
90+
title: 'The Alchemist',
91+
author: 'Paulo Coelho',
92+
year: 1988,
93+
},
94+
{
95+
id: 10,
96+
title: 'A Catcher in the Rye',
97+
author: 'J. D. Salinger',
98+
year: 1951,
99+
},
100+
{
101+
id: 11,
102+
title: 'Ulysses',
103+
author: 'James Joyce',
104+
year: 1922,
105+
},
106+
],
107+
authors: [],
108+
});
109+
110+
const history = createMemoryHistory({ initialEntries: ['/books'] });
111+
112+
const BookList = () => {
113+
return (
114+
<List>
115+
<Datagrid>
116+
<TextField source="id" />
117+
<TextField source="title" />
118+
<TextField source="author" />
119+
<TextField source="year" />
120+
<DeleteWithConfirmButton />
121+
</Datagrid>
122+
</List>
123+
);
124+
};
125+
126+
export const Basic = () => (
127+
<AdminContext
128+
dataProvider={dataProvider}
129+
i18nProvider={i18nProvider}
130+
history={history}
131+
>
132+
<AdminUI>
133+
<Resource name="books" list={BookList} />
134+
</AdminUI>
135+
</AdminContext>
136+
);

0 commit comments

Comments
 (0)