Skip to content

Commit 4ca9adb

Browse files
committed
Fix useCreate mutationMode cannot be controlled
1 parent da9499b commit 4ca9adb

File tree

3 files changed

+147
-2
lines changed

3 files changed

+147
-2
lines changed

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

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

55
import { RaRecord } from '../types';
66
import { testDataProvider } from './testDataProvider';
77
import { useCreate } from './useCreate';
88
import { useGetList } from './useGetList';
99
import { CoreAdminContext } from '../core';
10+
import { Basic } from './useCreate.stories';
1011
import {
1112
ErrorCase as ErrorCasePessimistic,
1213
SuccessCase as SuccessCasePessimistic,
@@ -334,6 +335,23 @@ describe('useCreate', () => {
334335
expect(screen.queryByText('mutating')).toBeNull();
335336
});
336337
});
338+
it('allows to control the mutation mode', async () => {
339+
jest.spyOn(console, 'error').mockImplementation(() => {});
340+
render(<Basic timeout={10} />);
341+
// Create a post in pessimistic mode
342+
fireEvent.click(await screen.findByText('Create post'));
343+
await screen.findByText('Hello World 2');
344+
fireEvent.click(await screen.findByText('undoable'));
345+
fireEvent.click(await screen.findByText('Increment id'));
346+
await screen.findByText('nothing yet');
347+
// Create a post in undoable mode
348+
fireEvent.click(await screen.findByText('Create post'));
349+
// Check the optimistic result
350+
await screen.findByText('Hello World 3');
351+
// As we haven't confirmed the undoable mutation, refetching the post should return nothing
352+
fireEvent.click(await screen.findByText('Refetch'));
353+
await screen.findByText('nothing yet');
354+
});
337355
});
338356

339357
describe('middlewares', () => {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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 { useCreate } from './useCreate';
7+
import { useGetOne } from './useGetOne';
8+
import { MutationMode } from '../types';
9+
10+
export default { title: 'ra-core/dataProvider/useCreate' };
11+
12+
export const Basic = ({ timeout = 1000 }: { timeout?: number }) => {
13+
const posts = [{ id: 1, title: 'Hello', author: 'John Doe' }];
14+
const dataProvider = {
15+
getOne: (resource, params) => {
16+
return new Promise((resolve, reject) => {
17+
const data = posts.find(p => p.id === params.id);
18+
setTimeout(() => {
19+
if (!data) {
20+
reject(new Error('nothing yet'));
21+
}
22+
resolve({ data });
23+
}, timeout);
24+
});
25+
},
26+
create: (resource, params) => {
27+
return new Promise(resolve => {
28+
setTimeout(() => {
29+
posts.push(params.data);
30+
resolve({ data: params.data });
31+
}, timeout);
32+
});
33+
},
34+
} as any;
35+
return (
36+
<CoreAdminContext
37+
queryClient={
38+
new QueryClient({
39+
defaultOptions: {
40+
queries: { retry: false },
41+
mutations: { retry: false },
42+
},
43+
})
44+
}
45+
dataProvider={dataProvider}
46+
>
47+
<SuccessCore />
48+
</CoreAdminContext>
49+
);
50+
};
51+
52+
Basic.args = {
53+
timeout: 1000,
54+
};
55+
56+
Basic.argTypes = {
57+
timeout: {
58+
control: {
59+
type: 'number',
60+
},
61+
},
62+
};
63+
64+
const SuccessCore = () => {
65+
const isMutating = useIsMutating();
66+
const [success, setSuccess] = useState<string>();
67+
const [id, setId] = useState<number>(2);
68+
const [mutationMode, setMutationMode] =
69+
useState<MutationMode>('pessimistic');
70+
const { data, error, refetch } = useGetOne('posts', { id });
71+
const [create, { isPending }] = useCreate(
72+
'posts',
73+
{},
74+
{
75+
mutationMode,
76+
onSuccess: () => {
77+
setSuccess('success');
78+
},
79+
}
80+
);
81+
const handleClick = () => {
82+
create('posts', {
83+
data: { id, title: `Hello World ${id}` },
84+
});
85+
};
86+
return (
87+
<>
88+
{error ? (
89+
<p>{error.message}</p>
90+
) : (
91+
<dl>
92+
<dt>id</dt>
93+
<dd>{data?.id}</dd>
94+
<dt>title</dt>
95+
<dd>{data?.title}</dd>
96+
<dt>author</dt>
97+
<dd>{data?.author}</dd>
98+
</dl>
99+
)}
100+
<div>
101+
<button onClick={handleClick} disabled={isPending}>
102+
Create post
103+
</button>
104+
&nbsp;
105+
<button onClick={() => refetch()}>Refetch</button>
106+
<button onClick={() => setId(prev => prev + 1)}>
107+
Increment id
108+
</button>
109+
<button onClick={() => setMutationMode('pessimistic')}>
110+
pessimistic
111+
</button>
112+
<button onClick={() => setMutationMode('optimistic')}>
113+
optimistic
114+
</button>
115+
<button onClick={() => setMutationMode('undoable')}>
116+
undoable
117+
</button>
118+
</div>
119+
{success && <div>{success}</div>}
120+
{isMutating !== 0 && <div>mutating</div>}
121+
</>
122+
);
123+
};

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

Lines changed: 5 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
UseMutationOptions,
@@ -96,7 +96,11 @@ export const useCreate = <
9696
getMutateWithMiddlewares,
9797
...mutationOptions
9898
} = options;
99+
99100
const mode = useRef<MutationMode>(mutationMode);
101+
useEffect(() => {
102+
mode.current = mutationMode;
103+
}, [mutationMode]);
100104

101105
const paramsRef =
102106
useRef<Partial<CreateParams<Partial<RecordType>>>>(params);

0 commit comments

Comments
 (0)