From 357b557811370969faee91fed6b1667e163fd7b3 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:40:24 +0200 Subject: [PATCH 1/6] Introduce `useDeleteController` --- .../ra-core/src/controller/button/index.ts | 1 + .../controller/button/useDeleteController.tsx | 184 ++++++++++++++++++ .../button/useDeleteWithConfirmController.tsx | 184 +++++++----------- .../button/useDeleteWithUndoController.tsx | 135 +++---------- .../src/button/DeleteWithConfirmButton.tsx | 102 +++++++--- .../src/button/DeleteWithUndoButton.tsx | 31 ++- 6 files changed, 371 insertions(+), 266 deletions(-) create mode 100644 packages/ra-core/src/controller/button/useDeleteController.tsx diff --git a/packages/ra-core/src/controller/button/index.ts b/packages/ra-core/src/controller/button/index.ts index 313e91771cd..926e94e5424 100644 --- a/packages/ra-core/src/controller/button/index.ts +++ b/packages/ra-core/src/controller/button/index.ts @@ -2,3 +2,4 @@ import useDeleteWithUndoController from './useDeleteWithUndoController'; import useDeleteWithConfirmController from './useDeleteWithConfirmController'; export { useDeleteWithUndoController, useDeleteWithConfirmController }; +export * from './useDeleteController'; diff --git a/packages/ra-core/src/controller/button/useDeleteController.tsx b/packages/ra-core/src/controller/button/useDeleteController.tsx new file mode 100644 index 00000000000..325694adc6b --- /dev/null +++ b/packages/ra-core/src/controller/button/useDeleteController.tsx @@ -0,0 +1,184 @@ +import { useCallback } from 'react'; +import { UseMutationOptions } from '@tanstack/react-query'; + +import { useDelete } from '../../dataProvider'; +import { useUnselect } from '../'; +import { useRedirect, RedirectionSideEffect } from '../../routing'; +import { useNotify } from '../../notification'; +import { RaRecord, MutationMode, DeleteParams } from '../../types'; +import { useResourceContext } from '../../core'; +import { useTranslate } from '../../i18n'; + +/** + * Prepare a set of callbacks for a delete button guarded by confirmation dialog + * + * @example + * + * const DeleteButton = ({ + * resource, + * record, + * redirect, + * onClick, + * ...rest + * }) => { + * const { + * open, + * isPending, + * handleDialogOpen, + * handleDialogClose, + * handleDelete, + * } = useDeleteWithConfirmController({ + * resource, + * record, + * redirect, + * onClick, + * }); + * + * return ( + * + * + * + * + * ); + * }; + */ +export const useDeleteController = < + RecordType extends RaRecord = any, + ErrorType = Error, +>( + props: UseDeleteControllerParams +): UseDeleteControllerReturn => { + const { mutationMode } = props; + const { + record, + redirect: redirectTo = 'list', + mutationOptions = {}, + successMessage, + } = props as UseDeleteControllerParams; + const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions; + const resource = useResourceContext(props); + const notify = useNotify(); + const unselect = useUnselect(resource); + const redirect = useRedirect(); + const translate = useTranslate(); + + const [deleteOne, { isPending }] = useDelete( + resource, + undefined, + { + onSuccess: () => { + notify( + successMessage ?? + `resources.${resource}.notifications.deleted`, + { + type: 'info', + messageArgs: { + smart_count: 1, + _: translate('ra.notification.deleted', { + smart_count: 1, + }), + }, + undoable: mutationMode === 'undoable', + } + ); + record && unselect([record.id]); + redirect(redirectTo, resource); + }, + onError: error => { + notify( + typeof error === 'string' + ? error + : (error as Error)?.message || + 'ra.notification.http_error', + { + type: 'error', + messageArgs: { + _: + typeof error === 'string' + ? error + : (error as Error)?.message + ? (error as Error).message + : undefined, + }, + } + ); + }, + } + ); + + const handleDelete = useCallback(() => { + if (!record) { + throw new Error( + 'The record cannot be deleted because no record has been passed' + ); + } + deleteOne( + resource, + { + id: record.id, + previousData: record, + meta: mutationMeta, + }, + { + mutationMode, + ...otherMutationOptions, + } + ); + }, [ + deleteOne, + mutationMeta, + mutationMode, + otherMutationOptions, + record, + resource, + ]); + + return { + isPending, + isLoading: isPending, + handleDelete, + }; +}; + +export interface UseDeleteControllerParams< + RecordType extends RaRecord = any, + MutationOptionsError = unknown, +> { + mutationMode?: MutationMode; + mutationOptions?: UseMutationOptions< + RecordType, + MutationOptionsError, + DeleteParams + >; + record?: RecordType; + redirect?: RedirectionSideEffect; + resource?: string; + successMessage?: string; +} + +export interface UseDeleteControllerReturn { + isLoading: boolean; + isPending: boolean; + handleDelete: () => void; +} diff --git a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx index 15b72f8f8d4..492727c4636 100644 --- a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx @@ -1,18 +1,16 @@ +import { useState, ReactEventHandler, SyntheticEvent } from 'react'; import { - useState, - useCallback, - ReactEventHandler, - SyntheticEvent, -} from 'react'; -import { UseMutationOptions } from '@tanstack/react-query'; - -import { useDelete } from '../../dataProvider'; -import { useUnselect } from '../../controller'; -import { useRedirect, RedirectionSideEffect } from '../../routing'; + useDeleteController, + UseDeleteControllerParams, + UseDeleteControllerReturn, + useUnselect, +} from '../'; +import { useRedirect } from '../../routing'; import { useNotify } from '../../notification'; -import { RaRecord, MutationMode, DeleteParams } from '../../types'; +import { RaRecord } from '../../types'; import { useResourceContext } from '../../core'; import { useTranslate } from '../../i18n'; +import { useEvent } from '../../util'; /** * Prepare a set of callbacks for a delete button guarded by confirmation dialog @@ -75,112 +73,85 @@ const useDeleteWithConfirmController = < props: UseDeleteWithConfirmControllerParams ): UseDeleteWithConfirmControllerReturn => { const { - record, - redirect: redirectTo = 'list', mutationMode, onClick, - mutationOptions = {}, + record, + redirect: redirectTo = 'list', successMessage, } = props; - const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions; - const resource = useResourceContext(props); const [open, setOpen] = useState(false); + const resource = useResourceContext(props); const notify = useNotify(); const unselect = useUnselect(resource); const redirect = useRedirect(); const translate = useTranslate(); - const [deleteOne, { isPending }] = useDelete( - resource, - undefined, - { - onSuccess: () => { - setOpen(false); - notify( - successMessage ?? - `resources.${resource}.notifications.deleted`, - { - type: 'info', - messageArgs: { - smart_count: 1, - _: translate('ra.notification.deleted', { + const { isPending, handleDelete: controllerHandleDelete } = + useDeleteController({ + mutationOptions: { + onSuccess: () => { + setOpen(false); + notify( + successMessage ?? + `resources.${resource}.notifications.deleted`, + { + type: 'info', + messageArgs: { smart_count: 1, - }), - }, - undoable: mutationMode === 'undoable', - } - ); - record && unselect([record.id]); - redirect(redirectTo, resource); - }, - onError: error => { - setOpen(false); + _: translate('ra.notification.deleted', { + smart_count: 1, + }), + }, + undoable: mutationMode === 'undoable', + } + ); + record && unselect([record.id]); + redirect(redirectTo, resource); + }, + onError: error => { + setOpen(false); - notify( - typeof error === 'string' - ? error - : (error as Error)?.message || - 'ra.notification.http_error', - { - type: 'error', - messageArgs: { - _: - typeof error === 'string' - ? error - : (error as Error)?.message - ? (error as Error).message - : undefined, - }, - } - ); + notify( + typeof error === 'string' + ? error + : (error as Error)?.message || + 'ra.notification.http_error', + { + type: 'error', + messageArgs: { + _: + typeof error === 'string' + ? error + : (error as Error)?.message + ? (error as Error).message + : undefined, + }, + } + ); + }, }, - } - ); + ...props, + }); - const handleDialogOpen = e => { - setOpen(true); + const handleDialogOpen = useEvent((e: any) => { e.stopPropagation(); - }; + setOpen(true); + }); - const handleDialogClose = e => { - setOpen(false); + const handleDialogClose = useEvent((e: any) => { e.stopPropagation(); - }; + setOpen(false); + }); - const handleDelete = useCallback( - event => { + const handleDelete = useEvent((event: any) => { + if (event && event.stopPropagation) { event.stopPropagation(); - if (!record) { - throw new Error( - 'The record cannot be deleted because no record has been passed' - ); - } - deleteOne( - resource, - { - id: record.id, - previousData: record, - meta: mutationMeta, - }, - { - mutationMode, - ...otherMutationOptions, - } - ); - if (typeof onClick === 'function') { - onClick(event); - } - }, - [ - deleteOne, - mutationMeta, - mutationMode, - otherMutationOptions, - onClick, - record, - resource, - ] - ); + } + controllerHandleDelete(); + if (typeof onClick === 'function') { + onClick(event); + } + }); return { open, @@ -195,24 +166,13 @@ const useDeleteWithConfirmController = < export interface UseDeleteWithConfirmControllerParams< RecordType extends RaRecord = any, MutationOptionsError = unknown, -> { - mutationMode?: MutationMode; - record?: RecordType; - redirect?: RedirectionSideEffect; - resource?: string; +> extends UseDeleteControllerParams { onClick?: ReactEventHandler; - mutationOptions?: UseMutationOptions< - RecordType, - MutationOptionsError, - DeleteParams - >; - successMessage?: string; } -export interface UseDeleteWithConfirmControllerReturn { +export interface UseDeleteWithConfirmControllerReturn + extends Omit { open: boolean; - isLoading: boolean; - isPending: boolean; handleDialogOpen: (e: SyntheticEvent) => void; handleDialogClose: (e: SyntheticEvent) => void; handleDelete: ReactEventHandler; diff --git a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx index f04445c4b86..ff6e6cb3a3b 100644 --- a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx @@ -1,13 +1,12 @@ -import { useCallback, ReactEventHandler } from 'react'; -import { UseMutationOptions } from '@tanstack/react-query'; +import type { ReactEventHandler } from 'react'; -import { useDelete } from '../../dataProvider'; -import { useUnselect } from '../../controller'; -import { useRedirect, RedirectionSideEffect } from '../../routing'; -import { useNotify } from '../../notification'; -import { RaRecord, DeleteParams } from '../../types'; -import { useResourceContext } from '../../core'; -import { useTranslate } from '../../i18n'; +import { + useDeleteController, + type UseDeleteControllerParams, + type UseDeleteControllerReturn, +} from './useDeleteController'; +import type { RaRecord } from '../../types'; +import { useEvent } from '../../util'; /** * Prepare callback for a Delete button with undo support @@ -50,96 +49,19 @@ const useDeleteWithUndoController = < >( props: UseDeleteWithUndoControllerParams ): UseDeleteWithUndoControllerReturn => { - const { - record, - redirect: redirectTo = 'list', - onClick, - mutationOptions = {}, - successMessage, - } = props; - const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions; - const resource = useResourceContext(props); - const notify = useNotify(); - const unselect = useUnselect(resource); - const redirect = useRedirect(); - const translate = useTranslate(); - const [deleteOne, { isPending }] = useDelete( - resource, - undefined, - { - onSuccess: () => { - notify( - successMessage ?? - `resources.${resource}.notifications.deleted`, - { - type: 'info', - messageArgs: { - smart_count: 1, - _: translate('ra.notification.deleted', { - smart_count: 1, - }), - }, - undoable: true, - } - ); - record && unselect([record.id]); - redirect(redirectTo, resource); - }, - onError: error => { - notify( - typeof error === 'string' - ? error - : (error as Error)?.message || - 'ra.notification.http_error', - { - type: 'error', - messageArgs: { - _: - typeof error === 'string' - ? error - : (error as Error)?.message - ? (error as Error).message - : undefined, - }, - } - ); - }, - } - ); + const { onClick } = props; + const { isPending, handleDelete: controllerHandleDelete } = + useDeleteController({ ...props, mutationMode: 'undoable' }); - const handleDelete = useCallback( - event => { + const handleDelete = useEvent((event: any) => { + if (event && event.stopPropagation) { event.stopPropagation(); - if (!record) { - throw new Error( - 'The record cannot be deleted because no record has been passed' - ); - } - deleteOne( - resource, - { - id: record.id, - previousData: record, - meta: mutationMeta, - }, - { - mutationMode: 'undoable', - ...otherMutationOptions, - } - ); - if (typeof onClick === 'function') { - onClick(event); - } - }, - [ - deleteOne, - mutationMeta, - otherMutationOptions, - onClick, - record, - resource, - ] - ); + } + controllerHandleDelete(); + if (typeof onClick === 'function') { + onClick(event); + } + }); return { isPending, isLoading: isPending, handleDelete }; }; @@ -147,22 +69,15 @@ const useDeleteWithUndoController = < export interface UseDeleteWithUndoControllerParams< RecordType extends RaRecord = any, MutationOptionsError = unknown, -> { - record?: RecordType; - redirect?: RedirectionSideEffect; - resource?: string; +> extends Omit< + UseDeleteControllerParams, + 'mutationMode' + > { onClick?: ReactEventHandler; - mutationOptions?: UseMutationOptions< - RecordType, - MutationOptionsError, - DeleteParams - >; - successMessage?: string; } -export interface UseDeleteWithUndoControllerReturn { - isPending: boolean; - isLoading: boolean; +export interface UseDeleteWithUndoControllerReturn + extends Omit { handleDelete: ReactEventHandler; } diff --git a/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx index 8e665c93bee..56bc9f3fcf7 100644 --- a/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.tsx @@ -7,18 +7,18 @@ import { } from '@mui/material/styles'; import clsx from 'clsx'; -import { UseMutationOptions } from '@tanstack/react-query'; import { - MutationMode, RaRecord, - DeleteParams, - useDeleteWithConfirmController, useRecordContext, useResourceContext, useTranslate, - RedirectionSideEffect, useGetRecordRepresentation, useResourceTranslation, + useDeleteController, + useNotify, + useUnselect, + useRedirect, + UseDeleteControllerParams, } from 'ra-core'; import { humanize, singularize } from 'inflection'; @@ -42,7 +42,7 @@ export const DeleteWithConfirmButton = ( label: labelProp, mutationMode = 'pessimistic', onClick, - redirect = 'list', + redirect: redirectTo = 'list', translateOptions = {}, titleTranslateOptions = translateOptions, contentTranslateOptions = translateOptions, @@ -54,27 +54,84 @@ export const DeleteWithConfirmButton = ( const translate = useTranslate(); const record = useRecordContext(props); const resource = useResourceContext(props); + const notify = useNotify(); + const unselect = useUnselect(resource); + const redirect = useRedirect(); + const [open, setOpen] = React.useState(false); if (!resource) { throw new Error( ' components should be used inside a component or provided with a resource prop. (The component set the resource prop for all its children).' ); } - const { - open, - isPending, - handleDialogOpen, - handleDialogClose, - handleDelete, - } = useDeleteWithConfirmController({ + const { onSuccess, onError, ...otherMutationOptions } = + mutationOptions || {}; + + const { isPending, handleDelete } = useDeleteController({ record, - redirect, + redirect: redirectTo, mutationMode, - onClick, - mutationOptions, + mutationOptions: { + ...otherMutationOptions, + onSuccess: (data, variables, context) => { + setOpen(false); + if (onSuccess) { + onSuccess(data, variables, context); + } else { + notify( + successMessage ?? + `resources.${resource}.notifications.deleted`, + { + type: 'info', + messageArgs: { + smart_count: 1, + _: translate('ra.notification.deleted', { + smart_count: 1, + }), + }, + undoable: mutationMode === 'undoable', + } + ); + record && unselect([record.id]); + redirect(redirectTo, resource); + } + }, + onError: (error, variables, context) => { + setOpen(false); + if (onError) { + onError(error, variables, context); + } else { + notify( + typeof error === 'string' + ? error + : (error as Error)?.message || + 'ra.notification.http_error', + { + type: 'error', + messageArgs: { + _: + typeof error === 'string' + ? error + : (error as Error)?.message + ? (error as Error).message + : undefined, + }, + } + ); + } + }, + }, resource, successMessage, }); + + const handleDialogOpen: ReactEventHandler = () => { + setOpen(true); + }; + const handleDialogClose: ReactEventHandler = () => { + setOpen(false); + }; + const getRecordRepresentation = useGetRecordRepresentation(resource); let recordRepresentation = getRecordRepresentation(record); const resourceName = translate(`resources.${resource}.forcedCaseName`, { @@ -156,12 +213,12 @@ const defaultIcon = ; export interface DeleteWithConfirmButtonProps< RecordType extends RaRecord = any, MutationOptionsError = unknown, -> extends ButtonProps { +> extends ButtonProps, + UseDeleteControllerParams { confirmTitle?: React.ReactNode; confirmContent?: React.ReactNode; icon?: React.ReactNode; confirmColor?: 'primary' | 'warning'; - mutationMode?: MutationMode; onClick?: ReactEventHandler; // May be injected by Toolbar - sanitized in Button /** @@ -170,15 +227,6 @@ export interface DeleteWithConfirmButtonProps< translateOptions?: object; titleTranslateOptions?: object; contentTranslateOptions?: object; - mutationOptions?: UseMutationOptions< - RecordType, - MutationOptionsError, - DeleteParams - >; - record?: RecordType; - redirect?: RedirectionSideEffect; - resource?: string; - successMessage?: string; } const PREFIX = 'RaDeleteWithConfirmButton'; diff --git a/packages/ra-ui-materialui/src/button/DeleteWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/DeleteWithUndoButton.tsx index 1b94740f331..e74544a1778 100644 --- a/packages/ra-ui-materialui/src/button/DeleteWithUndoButton.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteWithUndoButton.tsx @@ -2,17 +2,15 @@ import * as React from 'react'; import { ReactNode, ReactEventHandler } from 'react'; import ActionDelete from '@mui/icons-material/Delete'; import clsx from 'clsx'; -import { UseMutationOptions } from '@tanstack/react-query'; import { RaRecord, - useDeleteWithUndoController, - DeleteParams, + useDeleteController, useRecordContext, useResourceContext, - RedirectionSideEffect, useTranslate, useGetRecordRepresentation, useResourceTranslation, + UseDeleteControllerParams, } from 'ra-core'; import { humanize, singularize } from 'inflection'; @@ -50,14 +48,21 @@ export const DeleteWithUndoButton = ( ' components should be used inside a component or provided with a resource prop. (The component set the resource prop for all its children).' ); } - const { isPending, handleDelete } = useDeleteWithUndoController({ + const { isPending, handleDelete } = useDeleteController({ record, resource, redirect, - onClick, + mutationMode: 'undoable', mutationOptions, successMessage, }); + const handleClick: ReactEventHandler = event => { + handleDelete(); + if (onClick) { + onClick(event); + } + }; + const translate = useTranslate(); const getRecordRepresentation = useGetRecordRepresentation(resource); let recordRepresentation = getRecordRepresentation(record); @@ -87,7 +92,7 @@ export const DeleteWithUndoButton = ( return ( {label}} @@ -108,18 +113,10 @@ const defaultIcon = ; export interface DeleteWithUndoButtonProps< RecordType extends RaRecord = any, MutationOptionsError = unknown, -> extends ButtonProps { +> extends ButtonProps, + UseDeleteControllerParams { icon?: ReactNode; onClick?: ReactEventHandler; - mutationOptions?: UseMutationOptions< - RecordType, - MutationOptionsError, - DeleteParams - >; - record?: RecordType; - redirect?: RedirectionSideEffect; - resource?: string; - successMessage?: string; } const PREFIX = 'RaDeleteWithUndoButton'; From ecfc59c12aa74cd4f05256c0d500e0c155253443 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:53:29 +0200 Subject: [PATCH 2/6] Modify DeleteWithConfirmButton tests to check dialog is closed --- .../button/DeleteWithConfirmButton.spec.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.spec.tsx b/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.spec.tsx index 02bd5e4a4e8..80a1401b736 100644 --- a/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.spec.tsx +++ b/packages/ra-ui-materialui/src/button/DeleteWithConfirmButton.spec.tsx @@ -96,8 +96,8 @@ describe('', () => { it('should allow to override the resource', async () => { const dataProvider = testDataProvider({ - // @ts-ignore getOne: () => + // @ts-ignore Promise.resolve({ data: { id: 123, title: 'lorem' }, }), @@ -137,8 +137,8 @@ describe('', () => { it('should allows to undo the deletion after confirmation if mutationMode is undoable', async () => { const dataProvider = testDataProvider({ - // @ts-ignore getOne: () => + // @ts-ignore Promise.resolve({ data: { id: 123, title: 'lorem' }, }), @@ -182,8 +182,8 @@ describe('', () => { it('should allow to override the success side effects', async () => { const dataProvider = testDataProvider({ - // @ts-ignore getOne: () => + // @ts-ignore Promise.resolve({ data: { id: 123, title: 'lorem' }, }), @@ -226,13 +226,17 @@ describe('', () => { { snapshot: [] } ); }); + await waitFor(() => { + // Check that the dialog is closed + expect(screen.queryByText('ra.action.confirm')).toBeNull(); + }); }); it('should allow to override the error side effects', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); const dataProvider = testDataProvider({ - // @ts-ignore getOne: () => + // @ts-ignore Promise.resolve({ data: { id: 123, title: 'lorem' }, }), @@ -275,12 +279,16 @@ describe('', () => { { snapshot: [] } ); }); + await waitFor(() => { + // Check that the dialog is closed + expect(screen.queryByText('ra.action.confirm')).toBeNull(); + }); }); it('should allow to override the translateOptions props', async () => { const dataProvider = testDataProvider({ - // @ts-ignore getOne: () => + // @ts-ignore Promise.resolve({ data: { id: 123, title: 'lorem' }, }), @@ -322,8 +330,8 @@ describe('', () => { it('should display success message after successful deletion', async () => { const successMessage = 'Test Message'; const dataProvider = testDataProvider({ - // @ts-ignore getOne: () => + // @ts-ignore Promise.resolve({ data: { id: 123, title: 'lorem' }, }), From c1e4beae0734d63f240a6ed77e70b3755732de81 Mon Sep 17 00:00:00 2001 From: Gildas <1122076+djhi@users.noreply.github.com> Date: Mon, 25 Aug 2025 09:54:17 +0200 Subject: [PATCH 3/6] Apply code review --- .../controller/button/useDeleteController.tsx | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/ra-core/src/controller/button/useDeleteController.tsx b/packages/ra-core/src/controller/button/useDeleteController.tsx index 325694adc6b..073f2b5eb01 100644 --- a/packages/ra-core/src/controller/button/useDeleteController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteController.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { UseMutationOptions } from '@tanstack/react-query'; import { useDelete } from '../../dataProvider'; @@ -10,34 +10,31 @@ import { useResourceContext } from '../../core'; import { useTranslate } from '../../i18n'; /** - * Prepare a set of callbacks for a delete button guarded by confirmation dialog + * Prepare a set of callbacks for a delete button * * @example - * * const DeleteButton = ({ * resource, * record, * redirect, - * onClick, * ...rest * }) => { * const { - * open, * isPending, - * handleDialogOpen, - * handleDialogClose, * handleDelete, - * } = useDeleteWithConfirmController({ + * } = useDeleteController({ + * mutationMode: 'pessimistic', * resource, * record, * redirect, - * onClick, * }); * + * const [open, setOpen] = useState(false); + * * return ( * *