Skip to content

Commit 3623f3f

Browse files
committed
Introduce useUpdateController
1 parent 7df8d6a commit 3623f3f

File tree

3 files changed

+183
-59
lines changed

3 files changed

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

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

Lines changed: 6 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,15 @@ import {
66
} from '@mui/material/styles';
77
import ActionUpdate from '@mui/icons-material/Update';
88
import {
9-
useRefresh,
10-
useNotify,
119
useResourceContext,
1210
type RaRecord,
1311
useRecordContext,
14-
useUpdate,
15-
type UpdateParams,
1612
useTranslate,
1713
useGetRecordRepresentation,
1814
useResourceTranslation,
15+
useUpdateController,
16+
UseUpdateControllerParams,
1917
} from 'ra-core';
20-
import type { UseMutationOptions } from '@tanstack/react-query';
2118
import { humanize, singularize } from 'inflection';
2219

2320
import { Button, type ButtonProps } from './Button';
@@ -28,18 +25,16 @@ export const UpdateWithUndoButton = (inProps: UpdateWithUndoButtonProps) => {
2825
name: PREFIX,
2926
});
3027
const record = useRecordContext(props);
31-
const notify = useNotify();
3228
const resource = useResourceContext(props);
33-
const refresh = useRefresh();
3429

3530
const {
3631
data,
3732
label: labelProp,
3833
icon = defaultIcon,
3934
onClick,
40-
mutationOptions = {},
4135
...rest
4236
} = props;
37+
const { handleUpdate, isPending } = useUpdateController(rest);
4338
const translate = useTranslate();
4439
const getRecordRepresentation = useGetRecordRepresentation(resource);
4540
let recordRepresentation = getRecordRepresentation(record);
@@ -66,58 +61,14 @@ export const UpdateWithUndoButton = (inProps: UpdateWithUndoButtonProps) => {
6661
},
6762
userText: labelProp,
6863
});
69-
const [updateMany, { isPending }] = useUpdate();
70-
71-
const {
72-
meta: mutationMeta,
73-
onSuccess = () => {
74-
notify(`resources.${resource}.notifications.updated`, {
75-
type: 'info',
76-
messageArgs: {
77-
smart_count: 1,
78-
_: translate('ra.notification.updated', { smart_count: 1 }),
79-
},
80-
undoable: true,
81-
});
82-
},
83-
onError = (error: Error | string) => {
84-
notify(
85-
typeof error === 'string'
86-
? error
87-
: error.message || 'ra.notification.http_error',
88-
{
89-
type: 'error',
90-
messageArgs: {
91-
_:
92-
typeof error === 'string'
93-
? error
94-
: error && error.message
95-
? error.message
96-
: undefined,
97-
},
98-
}
99-
);
100-
refresh();
101-
},
102-
...otherMutationOptions
103-
} = mutationOptions;
10464

10565
const handleClick = e => {
10666
if (!record) {
10767
throw new Error(
10868
'The UpdateWithUndoButton must be used inside a RecordContext.Provider or must be passed a record prop.'
10969
);
11070
}
111-
updateMany(
112-
resource,
113-
{ id: record.id, data, meta: mutationMeta, previousData: record },
114-
{
115-
onSuccess,
116-
onError,
117-
mutationMode: 'undoable',
118-
...otherMutationOptions,
119-
}
120-
);
71+
handleUpdate();
12172
if (typeof onClick === 'function') {
12273
onClick(e);
12374
}
@@ -149,14 +100,10 @@ const sanitizeRestProps = ({
149100
export interface UpdateWithUndoButtonProps<
150101
RecordType extends RaRecord = any,
151102
MutationOptionsError = unknown,
152-
> extends ButtonProps {
103+
> extends ButtonProps,
104+
UseUpdateControllerParams<RecordType, MutationOptionsError> {
153105
icon?: React.ReactNode;
154106
data: any;
155-
mutationOptions?: UseMutationOptions<
156-
RecordType,
157-
MutationOptionsError,
158-
UpdateParams<RecordType>
159-
> & { meta?: any };
160107
}
161108

162109
const PREFIX = 'RaUpdateWithUndoButton';

0 commit comments

Comments
 (0)