Skip to content

Commit 9a5757c

Browse files
authored
Merge pull request #10923 from marmelab/use-bulk-delete-controller
Introduce `useBulkDeleteController`
2 parents c5cbf80 + 1819521 commit 9a5757c

File tree

6 files changed

+201
-170
lines changed

6 files changed

+201
-170
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: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
export const useBulkDeleteController = <
14+
RecordType extends RaRecord = any,
15+
ErrorType = Error,
16+
>(
17+
props: UseBulkDeleteControllerParams<RecordType, ErrorType>
18+
): UseBulkDeleteControllerReturn => {
19+
const {
20+
mutationMode = 'undoable',
21+
mutationOptions = {},
22+
successMessage,
23+
} = props;
24+
const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions;
25+
const resource = useResourceContext(props);
26+
const notify = useNotify();
27+
const refresh = useRefresh();
28+
const translate = useTranslate();
29+
const { selectedIds, onUnselectItems } = useListContext();
30+
31+
const [deleteMany, { isPending }] = useDeleteMany<RecordType, ErrorType>(
32+
resource,
33+
undefined,
34+
{
35+
onSuccess: () => {
36+
notify(
37+
successMessage ??
38+
`resources.${resource}.notifications.deleted`,
39+
{
40+
type: 'info',
41+
messageArgs: {
42+
smart_count: selectedIds.length,
43+
_: translate('ra.notification.deleted', {
44+
smart_count: selectedIds.length,
45+
}),
46+
},
47+
undoable: mutationMode === 'undoable',
48+
}
49+
);
50+
onUnselectItems();
51+
},
52+
onError: (error: any) => {
53+
notify(
54+
typeof error === 'string'
55+
? error
56+
: error?.message || 'ra.notification.http_error',
57+
{
58+
type: 'error',
59+
messageArgs: {
60+
_:
61+
typeof error === 'string'
62+
? error
63+
: error?.message,
64+
},
65+
}
66+
);
67+
refresh();
68+
},
69+
}
70+
);
71+
72+
const handleDelete = useCallback(() => {
73+
deleteMany(
74+
resource,
75+
{
76+
ids: selectedIds,
77+
meta: mutationMeta,
78+
},
79+
{
80+
mutationMode,
81+
...otherMutationOptions,
82+
}
83+
);
84+
}, [
85+
deleteMany,
86+
mutationMeta,
87+
mutationMode,
88+
otherMutationOptions,
89+
resource,
90+
selectedIds,
91+
]);
92+
93+
return useMemo(
94+
() => ({
95+
isPending,
96+
isLoading: isPending,
97+
handleDelete,
98+
}),
99+
[isPending, handleDelete]
100+
);
101+
};
102+
103+
export interface UseBulkDeleteControllerParams<
104+
RecordType extends RaRecord = any,
105+
MutationOptionsError = unknown,
106+
> {
107+
mutationMode?: MutationMode;
108+
mutationOptions?: UseDeleteManyOptions<RecordType, MutationOptionsError>;
109+
resource?: string;
110+
successMessage?: string;
111+
}
112+
113+
export interface UseBulkDeleteControllerReturn {
114+
isLoading: boolean;
115+
isPending: boolean;
116+
handleDelete: () => void;
117+
}

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

Lines changed: 9 additions & 21 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);
@@ -102,21 +97,18 @@ export const useDeleteController = <
10297
record && unselect([record.id]);
10398
redirect(redirectTo, resource);
10499
},
105-
onError: error => {
100+
onError: (error: any) => {
106101
notify(
107102
typeof error === 'string'
108103
? error
109-
: (error as Error)?.message ||
110-
'ra.notification.http_error',
104+
: error?.message || 'ra.notification.http_error',
111105
{
112106
type: 'error',
113107
messageArgs: {
114108
_:
115109
typeof error === 'string'
116110
? error
117-
: (error as Error)?.message
118-
? (error as Error).message
119-
: undefined,
111+
: error?.message,
120112
},
121113
}
122114
);
@@ -166,11 +158,7 @@ export interface UseDeleteControllerParams<
166158
MutationOptionsError = unknown,
167159
> {
168160
mutationMode?: MutationMode;
169-
mutationOptions?: UseMutationOptions<
170-
RecordType,
171-
MutationOptionsError,
172-
DeleteParams<RecordType>
173-
>;
161+
mutationOptions?: UseDeleteOptions<RecordType, MutationOptionsError>;
174162
record?: RecordType;
175163
redirect?: RedirectionSideEffect;
176164
resource?: string;

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
CoreAdminContext,
44
testDataProvider,
55
ListContextProvider,
6+
MutationMode,
67
} from 'ra-core';
78
import { createTheme, ThemeProvider } from '@mui/material/styles';
89

@@ -13,7 +14,11 @@ export default { title: 'ra-ui-materialui/button/BulkDeleteWithConfirmButton' };
1314

1415
const theme = createTheme();
1516

16-
export const Default = () => {
17+
export const Default = ({
18+
mutationMode = 'pessimistic',
19+
}: {
20+
mutationMode?: MutationMode;
21+
}) => {
1722
const dataProvider = testDataProvider({
1823
deleteMany: async () => ({ data: [{ id: 123 }] as any }),
1924
});
@@ -31,11 +36,24 @@ export const Default = () => {
3136
>
3237
<BulkDeleteWithConfirmButton
3338
resource="books"
34-
mutationMode="pessimistic"
39+
mutationMode={mutationMode}
3540
/>
3641
<Notification />
3742
</ListContextProvider>
3843
</ThemeProvider>
3944
</CoreAdminContext>
4045
);
4146
};
47+
48+
Default.args = {
49+
mutationMode: 'pessimistic',
50+
};
51+
52+
Default.argTypes = {
53+
mutationMode: {
54+
options: ['pessimistic', 'optimistic', 'undoable'],
55+
control: {
56+
type: 'select',
57+
},
58+
},
59+
};

0 commit comments

Comments
 (0)