Skip to content

Commit 71009e2

Browse files
committed
Fix useDeleteMany mutationMode cannot be controlled
1 parent 8d508c5 commit 71009e2

File tree

3 files changed

+130
-2
lines changed

3 files changed

+130
-2
lines changed

packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as React from 'react';
2-
import { waitFor, render } from '@testing-library/react';
2+
import { waitFor, render, screen, fireEvent } from '@testing-library/react';
33
import expect from 'expect';
44

55
import { CoreAdminContext } from '../core';
66
import { testDataProvider } from './testDataProvider';
77
import { useDeleteMany } from './useDeleteMany';
88
import { QueryClient } from '@tanstack/react-query';
9+
import { Basic } from './useDeleteMany.stories';
910

1011
describe('useDeleteMany', () => {
1112
it('returns a callback that can be used with update arguments', async () => {
@@ -263,4 +264,36 @@ describe('useDeleteMany', () => {
263264
});
264265
});
265266
});
267+
268+
it('allows to control the mutation mode', async () => {
269+
jest.spyOn(console, 'log').mockImplementation(() => {});
270+
jest.spyOn(console, 'error').mockImplementation(() => {});
271+
render(<Basic timeout={10} />);
272+
await screen.findByText('Hello World 1');
273+
await screen.findByText('Hello World 2');
274+
await screen.findByText('Hello World 3');
275+
await screen.findByText('Hello World 4');
276+
277+
// Delete the first 2 posts in pessimistic mode
278+
fireEvent.click(await screen.findByText('Delete posts'));
279+
// Wait for the post to be deleted
280+
await waitFor(() => {
281+
expect(screen.queryByText('Hello World 1')).toBeNull();
282+
expect(screen.queryByText('Hello World 2')).toBeNull();
283+
});
284+
285+
fireEvent.click(await screen.findByText('undoable'));
286+
fireEvent.click(await screen.findByText('Increment id'));
287+
// Delete the 2 next posts in undoable mode
288+
fireEvent.click(await screen.findByText('Delete posts'));
289+
// Check the optimistic result
290+
await waitFor(() => {
291+
expect(screen.queryByText('Hello World 3')).toBeNull();
292+
expect(screen.queryByText('Hello World 4')).toBeNull();
293+
});
294+
// As we haven't confirmed the undoable mutation, refetching the post should return nothing
295+
fireEvent.click(await screen.findByText('Refetch'));
296+
await screen.findByText('Hello World 3');
297+
await screen.findByText('Hello World 4');
298+
});
266299
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import * as React from 'react';
2+
import { useState } from 'react';
3+
import { QueryClient, useIsMutating } from '@tanstack/react-query';
4+
5+
import { CoreAdminContext } from '../core';
6+
import { useGetList } from './useGetList';
7+
import { MutationMode } from '../types';
8+
import { useDeleteMany } from './useDeleteMany';
9+
10+
export default { title: 'ra-core/dataProvider/useDeleteMany' };
11+
12+
export const Basic = ({ timeout = 1000 }: { timeout?: number }) => {
13+
const posts = [
14+
{ id: 1, title: 'Hello World 1' },
15+
{ id: 2, title: 'Hello World 2' },
16+
{ id: 3, title: 'Hello World 3' },
17+
{ id: 4, title: 'Hello World 4' },
18+
];
19+
const dataProvider = {
20+
getList: (resource, params) => {
21+
console.log('getList', resource, params);
22+
return Promise.resolve({
23+
data: posts,
24+
total: posts.length,
25+
});
26+
},
27+
deleteMany: (resource, params) => {
28+
console.log('deleteMany', resource, params);
29+
return new Promise(resolve => {
30+
setTimeout(() => {
31+
for (const id of params.ids) {
32+
const index = posts.findIndex(p => p.id === id);
33+
posts.splice(index, 1);
34+
}
35+
resolve({ data: params.ids });
36+
}, timeout);
37+
});
38+
},
39+
} as any;
40+
return (
41+
<CoreAdminContext
42+
queryClient={new QueryClient()}
43+
dataProvider={dataProvider}
44+
>
45+
<SuccessCore />
46+
</CoreAdminContext>
47+
);
48+
};
49+
50+
const SuccessCore = () => {
51+
const isMutating = useIsMutating();
52+
const [success, setSuccess] = useState<string>();
53+
const { data, refetch } = useGetList('posts');
54+
const [id, setId] = useState<number>(1);
55+
const [mutationMode, setMutationMode] =
56+
useState<MutationMode>('pessimistic');
57+
const [deleteMany, { isPending }] = useDeleteMany('posts', undefined, {
58+
mutationMode,
59+
onSuccess: () => setSuccess('success'),
60+
});
61+
const handleClick = () => {
62+
deleteMany('posts', {
63+
ids: [id, id + 1],
64+
});
65+
};
66+
return (
67+
<>
68+
<ul>{data?.map(post => <li key={post.id}>{post.title}</li>)}</ul>
69+
<div>
70+
<button onClick={handleClick} disabled={isPending}>
71+
Delete posts
72+
</button>
73+
&nbsp;
74+
<button onClick={() => refetch()}>Refetch</button>
75+
<button onClick={() => setId(prev => prev + 2)}>
76+
Increment id
77+
</button>
78+
<button onClick={() => setMutationMode('pessimistic')}>
79+
pessimistic
80+
</button>
81+
<button onClick={() => setMutationMode('optimistic')}>
82+
optimistic
83+
</button>
84+
<button onClick={() => setMutationMode('undoable')}>
85+
undoable
86+
</button>
87+
</div>
88+
{success && <div>{success}</div>}
89+
{isMutating !== 0 && <div>mutating</div>}
90+
</>
91+
);
92+
};

packages/ra-core/src/dataProvider/useDeleteMany.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo, useRef } from 'react';
1+
import { useEffect, useMemo, useRef } from 'react';
22
import {
33
useMutation,
44
useQueryClient,
@@ -93,6 +93,9 @@ export const useDeleteMany = <
9393
const { ids } = params;
9494
const { mutationMode = 'pessimistic', ...mutationOptions } = options;
9595
const mode = useRef<MutationMode>(mutationMode);
96+
useEffect(() => {
97+
mode.current = mutationMode;
98+
}, [mutationMode]);
9699
const paramsRef = useRef<Partial<DeleteManyParams<RecordType>>>({});
97100
const snapshot = useRef<Snapshot>([]);
98101
const hasCallTimeOnError = useRef(false);

0 commit comments

Comments
 (0)