diff --git a/docs/Buttons.md b/docs/Buttons.md index 060d22d3eb3..f9979595ca3 100644 --- a/docs/Buttons.md +++ b/docs/Buttons.md @@ -244,11 +244,11 @@ export const PostList = () => ( ![Bulk Delete button](./img/bulk-delete-button.png) -| Prop | Required | Type | Default | Description | -| ------------ | -------- | --------------- | ------------------ | ----------------------------------- | -| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use | -| `icon` | Optional | `ReactElement` | `` | iconElement, e.g. `` | -| `exporter` | Optional | `Function` | - | Override the List exporter function | +| Prop | Required | Type | Default | Description | +| --------------------| -------- | --------------- | ------------------ | ---------------------------------------------------| +| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use | +| `icon` | Optional | `ReactElement` | `` | iconElement, e.g. `` | +| `mutationOptions` | Optional | `object` | null | options for react-query `useMutation` hook | ### `` @@ -277,6 +277,7 @@ Delete the current record after a confirm dialog has been accepted. To be used i | `confirmContent` | Optional | `ReactNode` | 'ra.message.delete_content' | Message or React component to be used as the body of the confirm dialog | | `redirect` | Optional | `string | false | Function` | 'list' | Custom redirection after success side effect | | `translateOptions` | Optional | `{ id?: string, name?: string }` | {} | Custom id and name to be used in the confirm dialog's title | +| `mutationOptions` | Optional | | null | options for react-query `useMutation` hook | {% raw %} ```jsx diff --git a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.spec.tsx b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.spec.tsx new file mode 100644 index 00000000000..2b624854457 --- /dev/null +++ b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.spec.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import expect from 'expect'; +import { MemoryRouter, Route, Routes } from 'react-router'; +import { fireEvent, screen, render, waitFor } from '@testing-library/react'; + +import { testDataProvider } from '../../dataProvider'; +import { CoreAdminContext } from '../../core'; +import useDeleteWithConfirmController, { + UseDeleteWithConfirmControllerParams, +} from './useDeleteWithConfirmController'; + +describe('useDeleteWithConfirmController', () => { + it('should call the dataProvider.delete() function with the meta param', async () => { + let receivedMeta = null; + const dataProvider = testDataProvider({ + delete: jest.fn((ressource, params) => { + receivedMeta = params?.meta?.key; + return Promise.resolve({ data: params?.meta?.key }); + }), + }); + + const MockComponent = () => { + const { handleDelete } = useDeleteWithConfirmController({ + record: { id: 1 }, + resource: 'posts', + mutationMode: 'pessimistic', + mutationOptions: { meta: { key: 'metadata' } }, + } as UseDeleteWithConfirmControllerParams); + return ; + }; + + render( + + + + } /> + + + + ); + + const button = await screen.findByText('Delete'); + fireEvent.click(button); + waitFor(() => expect(receivedMeta).toEqual('metadata'), { + timeout: 1000, + }); + }); +}); diff --git a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx index df5fd83c971..3bd9cc09541 100644 --- a/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteWithConfirmController.tsx @@ -71,8 +71,9 @@ const useDeleteWithConfirmController = ( redirect: redirectTo, mutationMode, onClick, - mutationOptions, + mutationOptions = {}, } = props; + const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions; const resource = useResourceContext(props); const [open, setOpen] = useState(false); const notify = useNotify(); @@ -95,7 +96,11 @@ const useDeleteWithConfirmController = ( event.stopPropagation(); deleteOne( resource, - { id: record.id, previousData: record }, + { + id: record.id, + previousData: record, + meta: mutationMeta, + }, { onSuccess: () => { setOpen(false); @@ -128,7 +133,7 @@ const useDeleteWithConfirmController = ( ); }, mutationMode, - ...mutationOptions, + ...otherMutationOptions, } ); if (typeof onClick === 'function') { @@ -137,8 +142,9 @@ const useDeleteWithConfirmController = ( }, [ deleteOne, + mutationMeta, mutationMode, - mutationOptions, + otherMutationOptions, notify, onClick, record, diff --git a/packages/ra-core/src/controller/button/useDeleteWithUndoController.spec.tsx b/packages/ra-core/src/controller/button/useDeleteWithUndoController.spec.tsx new file mode 100644 index 00000000000..b3ec571e384 --- /dev/null +++ b/packages/ra-core/src/controller/button/useDeleteWithUndoController.spec.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import expect from 'expect'; +import { MemoryRouter, Route, Routes } from 'react-router'; +import { fireEvent, screen, render, waitFor } from '@testing-library/react'; + +import { testDataProvider } from '../../dataProvider'; +import { CoreAdminContext } from '../../core'; +import useDeleteWithUndoController, { + UseDeleteWithConfirmControllerParams, +} from './useDeleteWithConfirmController'; + +describe('useDeleteWithUndoController', () => { + it('should call the dataProvider.delete() function with the meta param', async () => { + let receivedMeta = null; + const dataProvider = testDataProvider({ + delete: jest.fn((ressource, params) => { + receivedMeta = params?.meta?.key; + return Promise.resolve({ data: params?.meta?.key }); + }), + }); + + const MockComponent = () => { + const { handleDelete } = useDeleteWithUndoController({ + record: { id: 1 }, + resource: 'posts', + mutationMode: 'undoable', + mutationOptions: { meta: { key: 'metadata' } }, + } as UseDeleteWithConfirmControllerParams); + return ; + }; + + render( + + + + } /> + + + + ); + + const button = await screen.findByText('Delete'); + fireEvent.click(button); + waitFor(() => expect(receivedMeta).toEqual('metadata'), { + timeout: 1000, + }); + }); +}); diff --git a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx index 2b1f6e2117c..a7c8af9bc0d 100644 --- a/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx +++ b/packages/ra-core/src/controller/button/useDeleteWithUndoController.tsx @@ -50,8 +50,9 @@ const useDeleteWithUndoController = ( record, redirect: redirectTo = 'list', onClick, - mutationOptions, + mutationOptions = {}, } = props; + const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions; const resource = useResourceContext(props); const notify = useNotify(); const unselect = useUnselect(resource); @@ -63,7 +64,11 @@ const useDeleteWithUndoController = ( event.stopPropagation(); deleteOne( resource, - { id: record.id, previousData: record }, + { + id: record.id, + previousData: record, + meta: mutationMeta, + }, { onSuccess: () => { notify('ra.notification.deleted', { @@ -93,7 +98,7 @@ const useDeleteWithUndoController = ( ); }, mutationMode: 'undoable', - ...mutationOptions, + ...otherMutationOptions, } ); if (typeof onClick === 'function') { @@ -102,7 +107,8 @@ const useDeleteWithUndoController = ( }, [ deleteOne, - mutationOptions, + mutationMeta, + otherMutationOptions, notify, onClick, record, diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx index 03d465f57fc..d6517116343 100644 --- a/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithConfirmButton.tsx @@ -14,11 +14,14 @@ import { useTranslate, useUnselectAll, useSafeSetState, + RaRecord, + DeleteManyParams, } from 'ra-core'; import { Confirm } from '../layout'; import { Button, ButtonProps } from './Button'; import { BulkActionProps } from '../types'; +import { UseMutationOptions } from 'react-query'; export const BulkDeleteWithConfirmButton = ( props: BulkDeleteWithConfirmButtonProps @@ -29,9 +32,11 @@ export const BulkDeleteWithConfirmButton = ( icon = defaultIcon, label = 'ra.action.delete', mutationMode = 'pessimistic', + mutationOptions = {}, onClick, ...rest } = props; + const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions; const { selectedIds } = useListContext(props); const [isOpen, setOpen] = useSafeSetState(false); const notify = useNotify(); @@ -41,7 +46,7 @@ export const BulkDeleteWithConfirmButton = ( const translate = useTranslate(); const [deleteMany, { isLoading }] = useDeleteMany( resource, - { ids: selectedIds }, + { ids: selectedIds, meta: mutationMeta }, { onSuccess: () => { refresh(); @@ -73,6 +78,7 @@ export const BulkDeleteWithConfirmButton = ( setOpen(false); }, mutationMode, + ...otherMutationOptions, } ); @@ -141,13 +147,20 @@ const sanitizeRestProps = ({ 'resource' | 'icon' | 'mutationMode' >) => rest; -export interface BulkDeleteWithConfirmButtonProps - extends BulkActionProps, +export interface BulkDeleteWithConfirmButtonProps< + RecordType extends RaRecord = any, + MutationOptionsError = unknown +> extends BulkActionProps, ButtonProps { confirmContent?: React.ReactNode; confirmTitle?: string; icon?: ReactElement; mutationMode: MutationMode; + mutationOptions?: UseMutationOptions< + RecordType, + MutationOptionsError, + DeleteManyParams + > & { meta?: any }; } const PREFIX = 'RaBulkDeleteWithConfirmButton'; diff --git a/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx b/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx index 42385231af4..9876404c80b 100644 --- a/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx +++ b/packages/ra-ui-materialui/src/button/BulkDeleteWithUndoButton.tsx @@ -11,10 +11,13 @@ import { useUnselectAll, useResourceContext, useListContext, + RaRecord, + DeleteManyParams, } from 'ra-core'; import { Button, ButtonProps } from './Button'; import { BulkActionProps } from '../types'; +import { UseMutationOptions } from 'react-query'; export const BulkDeleteWithUndoButton = ( props: BulkDeleteWithUndoButtonProps @@ -23,8 +26,10 @@ export const BulkDeleteWithUndoButton = ( label = 'ra.action.delete', icon = defaultIcon, onClick, + mutationOptions = {}, ...rest } = props; + const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions; const { selectedIds } = useListContext(props); const notify = useNotify(); @@ -36,7 +41,7 @@ export const BulkDeleteWithUndoButton = ( const handleClick = e => { deleteMany( resource, - { ids: selectedIds }, + { ids: selectedIds, meta: mutationMeta }, { onSuccess: () => { notify('ra.notification.deleted', { @@ -66,6 +71,7 @@ export const BulkDeleteWithUndoButton = ( refresh(); }, mutationMode: 'undoable', + ...otherMutationOptions, } ); if (typeof onClick === 'function') { @@ -95,10 +101,17 @@ const sanitizeRestProps = ({ ...rest }: Omit) => rest; -export interface BulkDeleteWithUndoButtonProps - extends BulkActionProps, +export interface BulkDeleteWithUndoButtonProps< + RecordType extends RaRecord = any, + MutationOptionsError = unknown +> extends BulkActionProps, ButtonProps { icon?: ReactElement; + mutationOptions?: UseMutationOptions< + RecordType, + MutationOptionsError, + DeleteManyParams + > & { meta?: any }; } const PREFIX = 'RaBulkDeleteWithUndoButton';