Skip to content

Commit 8d508c5

Browse files
committed
Fix useDelete mutationMode cannot be controlled
1 parent 4ca9adb commit 8d508c5

File tree

3 files changed

+124
-2
lines changed

3 files changed

+124
-2
lines changed

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

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

55
import { CoreAdminContext } from '../core';
@@ -20,6 +20,7 @@ import {
2020
SuccessCase as SuccessCaseUndoable,
2121
} from './useDelete.undoable.stories';
2222
import { QueryClient } from '@tanstack/react-query';
23+
import { Basic } from './useDelete.stories';
2324

2425
describe('useDelete', () => {
2526
it('returns a callback that can be used with deleteOne arguments', async () => {
@@ -449,6 +450,31 @@ describe('useDelete', () => {
449450
{ timeout: 4000 }
450451
);
451452
});
453+
it('allows to control the mutation mode', async () => {
454+
jest.spyOn(console, 'error').mockImplementation(() => {});
455+
render(<Basic timeout={10} />);
456+
// Delete the first post in pessimistic mode
457+
await screen.findByText('Hello');
458+
await screen.findByText('World');
459+
fireEvent.click(await screen.findByText('Delete post'));
460+
await screen.findByText('World');
461+
// Wait for the post to be deleted
462+
await waitFor(() => {
463+
expect(screen.queryByText('Hello')).toBeNull();
464+
});
465+
466+
fireEvent.click(await screen.findByText('undoable'));
467+
fireEvent.click(await screen.findByText('Increment id'));
468+
// Delete the second post in undoable mode
469+
fireEvent.click(await screen.findByText('Delete post'));
470+
// Check the optimistic result
471+
await waitFor(() => {
472+
expect(screen.queryByText('World')).toBeNull();
473+
});
474+
// As we haven't confirmed the undoable mutation, refetching the post should return nothing
475+
fireEvent.click(await screen.findByText('Refetch'));
476+
await screen.findByText('World');
477+
});
452478
});
453479

454480
describe('query cache', () => {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 { useDelete } from './useDelete';
7+
import { useGetList } from './useGetList';
8+
import { MutationMode } from '../types';
9+
10+
export default { title: 'ra-core/dataProvider/useDelete' };
11+
12+
export const Basic = ({ timeout = 1000 }: { timeout?: number }) => {
13+
const posts = [
14+
{ id: 1, title: 'Hello' },
15+
{ id: 2, title: 'World' },
16+
];
17+
const dataProvider = {
18+
getList: (resource, params) => {
19+
console.log('getList', resource, params);
20+
return Promise.resolve({
21+
data: posts,
22+
total: posts.length,
23+
});
24+
},
25+
delete: (resource, params) => {
26+
console.log('delete', resource, params);
27+
return new Promise(resolve => {
28+
setTimeout(() => {
29+
const index = posts.findIndex(p => p.id === params.id);
30+
const deletedPost = posts.splice(index, 1);
31+
resolve({ data: deletedPost });
32+
}, timeout);
33+
});
34+
},
35+
} as any;
36+
return (
37+
<CoreAdminContext
38+
queryClient={new QueryClient()}
39+
dataProvider={dataProvider}
40+
>
41+
<SuccessCore />
42+
</CoreAdminContext>
43+
);
44+
};
45+
46+
const SuccessCore = () => {
47+
const isMutating = useIsMutating();
48+
const [success, setSuccess] = useState<string>();
49+
const { data, refetch } = useGetList('posts');
50+
const [id, setId] = useState<number>(1);
51+
const [mutationMode, setMutationMode] =
52+
useState<MutationMode>('pessimistic');
53+
const [deleteOne, { isPending }] = useDelete(
54+
'posts',
55+
{},
56+
{
57+
mutationMode,
58+
onSuccess: () => setSuccess('success'),
59+
}
60+
);
61+
const handleClick = () => {
62+
deleteOne('posts', {
63+
id,
64+
previousData: { id, title: 'Hello' },
65+
});
66+
};
67+
return (
68+
<>
69+
<ul>{data?.map(post => <li key={post.id}>{post.title}</li>)}</ul>
70+
<div>
71+
<button onClick={handleClick} disabled={isPending}>
72+
Delete post
73+
</button>
74+
&nbsp;
75+
<button onClick={() => refetch()}>Refetch</button>
76+
<button onClick={() => setId(prev => prev + 1)}>
77+
Increment id
78+
</button>
79+
<button onClick={() => setMutationMode('pessimistic')}>
80+
pessimistic
81+
</button>
82+
<button onClick={() => setMutationMode('optimistic')}>
83+
optimistic
84+
</button>
85+
<button onClick={() => setMutationMode('undoable')}>
86+
undoable
87+
</button>
88+
</div>
89+
{success && <div>{success}</div>}
90+
{isMutating !== 0 && <div>mutating</div>}
91+
</>
92+
);
93+
};

packages/ra-core/src/dataProvider/useDelete.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 useDelete = <
9393
const { id, previousData } = 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<DeleteParams<RecordType>>>(params);
97100
const snapshot = useRef<Snapshot>([]);
98101
const hasCallTimeOnError = useRef(false);

0 commit comments

Comments
 (0)