Skip to content

Commit 57dc570

Browse files
committed
Introduce useBulkDeleteController
1 parent ff6b694 commit 57dc570

File tree

5 files changed

+204
-159
lines changed

5 files changed

+204
-159
lines changed

packages/ra-core/src/controller/button/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ import useDeleteWithConfirmController from './useDeleteWithConfirmController';
33

44
export { useDeleteWithUndoController, useDeleteWithConfirmController };
55
export * from './useDeleteController';
6+
export * from './useBulkDeleteController';
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { useCallback, useMemo } from 'react';
2+
import {
3+
useDeleteMany,
4+
UseDeleteManyOptions,
5+
} from '../../dataProvider/useDeleteMany';
6+
import { useRefresh } from '../../dataProvider/useRefresh';
7+
import { useListContext } from '../list/useListContext';
8+
import { useNotify } from '../../notification/useNotify';
9+
import { RaRecord, MutationMode } from '../../types';
10+
import { useResourceContext } from '../../core/useResourceContext';
11+
import { useTranslate } from '../../i18n/useTranslate';
12+
13+
/**
14+
* Prepare a set of callbacks for a delete button
15+
*
16+
* @example
17+
* const DeleteButton = ({
18+
* resource,
19+
* record,
20+
* redirect,
21+
* ...rest
22+
* }) => {
23+
* const {
24+
* isPending,
25+
* handleDelete,
26+
* } = useDeleteController({
27+
* mutationMode: 'pessimistic',
28+
* resource,
29+
* record,
30+
* redirect,
31+
* });
32+
*
33+
* const [open, setOpen] = useState(false);
34+
*
35+
* return (
36+
* <Fragment>
37+
* <Button
38+
* onClick={() => setOpen(true)}
39+
* label="ra.action.delete"
40+
* {...rest}
41+
* >
42+
* {icon}
43+
* </Button>
44+
* <Confirm
45+
* isOpen={open}
46+
* loading={isPending}
47+
* title="ra.message.delete_title"
48+
* content="ra.message.delete_content"
49+
* titleTranslateOptions={{
50+
* name: resource,
51+
* id: record.id,
52+
* }}
53+
* contentTranslateOptions={{
54+
* name: resource,
55+
* id: record.id,
56+
* }}
57+
* onConfirm={() => handleDelete()}
58+
* onClose={() => setOpen(false)}
59+
* />
60+
* </Fragment>
61+
* );
62+
* };
63+
*/
64+
export const useBulkDeleteController = <
65+
RecordType extends RaRecord = any,
66+
ErrorType = Error,
67+
>(
68+
props: UseBulkDeleteControllerParams<RecordType, ErrorType>
69+
): UseBulkDeleteControllerReturn => {
70+
const {
71+
mutationMode = 'undoable',
72+
mutationOptions = {},
73+
successMessage,
74+
} = props;
75+
const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions;
76+
const resource = useResourceContext(props);
77+
const notify = useNotify();
78+
const refresh = useRefresh();
79+
const translate = useTranslate();
80+
const { selectedIds, onUnselectItems } = useListContext();
81+
82+
const [deleteMany, { isPending }] = useDeleteMany<RecordType, ErrorType>(
83+
resource,
84+
undefined,
85+
{
86+
onSuccess: () => {
87+
notify(
88+
successMessage ??
89+
`resources.${resource}.notifications.deleted`,
90+
{
91+
type: 'info',
92+
messageArgs: {
93+
smart_count: selectedIds.length,
94+
_: translate('ra.notification.deleted', {
95+
smart_count: selectedIds.length,
96+
}),
97+
},
98+
undoable: mutationMode === 'undoable',
99+
}
100+
);
101+
onUnselectItems();
102+
},
103+
onError: (error: any) => {
104+
notify(
105+
typeof error === 'string'
106+
? error
107+
: error.message || 'ra.notification.http_error',
108+
{
109+
type: 'error',
110+
messageArgs: {
111+
_:
112+
typeof error === 'string'
113+
? error
114+
: error && error.message
115+
? error.message
116+
: undefined,
117+
},
118+
}
119+
);
120+
refresh();
121+
},
122+
}
123+
);
124+
125+
const handleDelete = useCallback(() => {
126+
deleteMany(
127+
resource,
128+
{
129+
ids: selectedIds,
130+
meta: mutationMeta,
131+
},
132+
{
133+
mutationMode,
134+
...otherMutationOptions,
135+
}
136+
);
137+
}, [
138+
deleteMany,
139+
mutationMeta,
140+
mutationMode,
141+
otherMutationOptions,
142+
resource,
143+
selectedIds,
144+
]);
145+
146+
return useMemo(
147+
() => ({
148+
isPending,
149+
isLoading: isPending,
150+
handleDelete,
151+
}),
152+
[isPending, handleDelete]
153+
);
154+
};
155+
156+
export interface UseBulkDeleteControllerParams<
157+
RecordType extends RaRecord = any,
158+
MutationOptionsError = unknown,
159+
> {
160+
mutationMode?: MutationMode;
161+
mutationOptions?: UseDeleteManyOptions<RecordType, MutationOptionsError>;
162+
resource?: string;
163+
successMessage?: string;
164+
}
165+
166+
export interface UseBulkDeleteControllerReturn {
167+
isLoading: boolean;
168+
isPending: boolean;
169+
handleDelete: () => void;
170+
}

packages/ra-core/src/controller/button/useDeleteController.tsx

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { useCallback, useMemo } from 'react';
2-
import { UseMutationOptions } from '@tanstack/react-query';
32

4-
import { useDelete } from '../../dataProvider';
5-
import { useUnselect } from '../';
3+
import { useDelete, UseDeleteOptions } from '../../dataProvider';
4+
import { useRecordContext, useUnselect } from '../';
65
import { useRedirect, RedirectionSideEffect } from '../../routing';
76
import { useNotify } from '../../notification';
8-
import { RaRecord, MutationMode, DeleteParams } from '../../types';
7+
import { RaRecord, MutationMode } from '../../types';
98
import { useResourceContext } from '../../core';
109
import { useTranslate } from '../../i18n';
1110

@@ -14,8 +13,6 @@ import { useTranslate } from '../../i18n';
1413
*
1514
* @example
1615
* const DeleteButton = ({
17-
* resource,
18-
* record,
1916
* redirect,
2017
* ...rest
2118
* }) => {
@@ -24,8 +21,6 @@ import { useTranslate } from '../../i18n';
2421
* handleDelete,
2522
* } = useDeleteController({
2623
* mutationMode: 'pessimistic',
27-
* resource,
28-
* record,
2924
* redirect,
3025
* });
3126
*
@@ -67,13 +62,13 @@ export const useDeleteController = <
6762
props: UseDeleteControllerParams<RecordType, ErrorType>
6863
): UseDeleteControllerReturn => {
6964
const {
70-
record,
7165
redirect: redirectTo = 'list',
72-
mutationMode,
66+
mutationMode = 'undoable',
7367
mutationOptions = {},
7468
successMessage,
7569
} = props;
7670
const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions;
71+
const record = useRecordContext(props);
7772
const resource = useResourceContext(props);
7873
const notify = useNotify();
7974
const unselect = useUnselect(resource);
@@ -166,11 +161,7 @@ export interface UseDeleteControllerParams<
166161
MutationOptionsError = unknown,
167162
> {
168163
mutationMode?: MutationMode;
169-
mutationOptions?: UseMutationOptions<
170-
RecordType,
171-
MutationOptionsError,
172-
DeleteParams<RecordType>
173-
>;
164+
mutationOptions?: UseDeleteOptions<RecordType, MutationOptionsError>;
174165
record?: RecordType;
175166
redirect?: RedirectionSideEffect;
176167
resource?: string;

packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx

Lines changed: 18 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,16 @@ import {
88
useThemeProps,
99
} from '@mui/material/styles';
1010
import {
11-
type MutationMode,
12-
useDeleteMany,
13-
useListContext,
14-
useNotify,
15-
useRefresh,
1611
useResourceContext,
1712
useTranslate,
1813
type RaRecord,
19-
type DeleteManyParams,
14+
UseBulkDeleteControllerParams,
15+
useBulkDeleteController,
16+
useListContext,
2017
} from 'ra-core';
2118

2219
import { Confirm } from '../layout';
2320
import { Button, type ButtonProps } from './Button';
24-
import type { UseMutationOptions } from '@tanstack/react-query';
2521
import { humanize, inflect } from 'inflection';
2622

2723
export const BulkDeleteWithConfirmButton = (
@@ -38,64 +34,18 @@ export const BulkDeleteWithConfirmButton = (
3834
icon = defaultIcon,
3935
label = 'ra.action.delete',
4036
mutationMode = 'pessimistic',
41-
mutationOptions = {},
42-
successMessage,
4337
onClick,
4438
...rest
4539
} = props;
46-
const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions;
47-
const { selectedIds, onUnselectItems } = useListContext();
40+
const { selectedIds } = useListContext();
41+
const { handleDelete, isPending } = useBulkDeleteController({
42+
mutationMode,
43+
...rest,
44+
});
45+
4846
const [isOpen, setOpen] = useState(false);
49-
const notify = useNotify();
5047
const resource = useResourceContext(props);
51-
const refresh = useRefresh();
5248
const translate = useTranslate();
53-
const [deleteMany, { isPending }] = useDeleteMany(
54-
resource,
55-
{ ids: selectedIds, meta: mutationMeta },
56-
{
57-
onSuccess: () => {
58-
refresh();
59-
notify(
60-
successMessage ??
61-
`resources.${resource}.notifications.deleted`,
62-
{
63-
type: 'info',
64-
messageArgs: {
65-
smart_count: selectedIds.length,
66-
_: translate('ra.notification.deleted', {
67-
smart_count: selectedIds.length,
68-
}),
69-
},
70-
undoable: mutationMode === 'undoable',
71-
}
72-
);
73-
onUnselectItems();
74-
setOpen(false);
75-
},
76-
onError: (error: Error) => {
77-
notify(
78-
typeof error === 'string'
79-
? error
80-
: error.message || 'ra.notification.http_error',
81-
{
82-
type: 'error',
83-
messageArgs: {
84-
_:
85-
typeof error === 'string'
86-
? error
87-
: error && error.message
88-
? error.message
89-
: undefined,
90-
},
91-
}
92-
);
93-
setOpen(false);
94-
},
95-
mutationMode,
96-
...otherMutationOptions,
97-
}
98-
);
9949

10050
const handleClick = e => {
10151
setOpen(true);
@@ -106,8 +56,9 @@ export const BulkDeleteWithConfirmButton = (
10656
setOpen(false);
10757
};
10858

109-
const handleDelete = e => {
110-
deleteMany();
59+
const handleConfirm = e => {
60+
setOpen(false);
61+
handleDelete();
11162

11263
if (typeof onClick === 'function') {
11364
onClick(e);
@@ -159,7 +110,7 @@ export const BulkDeleteWithConfirmButton = (
159110
),
160111
}),
161112
}}
162-
onConfirm={handleDelete}
113+
onConfirm={handleConfirm}
163114
onClose={handleDialogClose}
164115
/>
165116
</Fragment>
@@ -169,27 +120,20 @@ export const BulkDeleteWithConfirmButton = (
169120
const sanitizeRestProps = ({
170121
classes,
171122
label,
123+
resource,
124+
successMessage,
172125
...rest
173-
}: Omit<
174-
BulkDeleteWithConfirmButtonProps,
175-
'resource' | 'icon' | 'mutationMode'
176-
>) => rest;
126+
}: Omit<BulkDeleteWithConfirmButtonProps, 'icon' | 'mutationMode'>) => rest;
177127

178128
export interface BulkDeleteWithConfirmButtonProps<
179129
RecordType extends RaRecord = any,
180130
MutationOptionsError = unknown,
181-
> extends ButtonProps {
131+
> extends ButtonProps,
132+
UseBulkDeleteControllerParams<RecordType, MutationOptionsError> {
182133
confirmContent?: React.ReactNode;
183134
confirmTitle?: React.ReactNode;
184135
confirmColor?: 'primary' | 'warning';
185136
icon?: React.ReactNode;
186-
mutationMode: MutationMode;
187-
mutationOptions?: UseMutationOptions<
188-
RecordType,
189-
MutationOptionsError,
190-
DeleteManyParams<RecordType>
191-
> & { meta?: any };
192-
successMessage?: string;
193137
}
194138

195139
const PREFIX = 'RaBulkDeleteWithConfirmButton';

0 commit comments

Comments
 (0)