Skip to content

Commit

Permalink
Fix useUpdate ignores meta when populating the query cache in pes…
Browse files Browse the repository at this point in the history
…simistic mode
  • Loading branch information
slax57 committed Dec 18, 2024
1 parent dabb850 commit fef8540
Show file tree
Hide file tree
Showing 2 changed files with 369 additions and 1 deletion.
367 changes: 367 additions & 0 deletions packages/ra-core/src/dataProvider/useUpdate.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,374 @@ describe('useUpdate', () => {
});
});
});
describe('pessimistic mutation mode', () => {
it('updates getOne query cache when dataProvider promise resolves', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(
['foo', 'getOne', { id: '1', meta: undefined }],
{ id: 1, bar: 'bar' }
);
const dataProvider = {
update: jest.fn(() =>
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
),
} as any;
let localUpdate;
const Dummy = () => {
const [update] = useUpdate();
localUpdate = update;
return <span />;
};
render(
<CoreAdminContext
dataProvider={dataProvider}
queryClient={queryClient}
>
<Dummy />
</CoreAdminContext>
);
localUpdate('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
});
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
});
});
await waitFor(() => {
expect(
queryClient.getQueryData([
'foo',
'getOne',
{ id: '1', meta: undefined },
])
).toEqual({
id: 1,
bar: 'baz',
});
});
});

it('updates getOne query cache when dataProvider promise resolves with meta', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(
['foo', 'getOne', { id: '1', meta: { key: 'value' } }],
{ id: 1, bar: 'bar' }
);
const dataProvider = {
update: jest.fn(() =>
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
),
} as any;
let localUpdate;
const Dummy = () => {
const [update] = useUpdate();
localUpdate = update;
return <span />;
};
render(
<CoreAdminContext
dataProvider={dataProvider}
queryClient={queryClient}
>
<Dummy />
</CoreAdminContext>
);
localUpdate('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
meta: { key: 'value' },
});
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
meta: { key: 'value' },
});
});
await waitFor(() => {
expect(
queryClient.getQueryData([
'foo',
'getOne',
{ id: '1', meta: { key: 'value' } },
])
).toEqual({
id: 1,
bar: 'baz',
});
});
});

it('updates getOne query cache when dataProvider promise resolves with meta at hook time', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(
['foo', 'getOne', { id: '1', meta: { key: 'value' } }],
{ id: 1, bar: 'bar' }
);
const dataProvider = {
update: jest.fn(() =>
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
),
} as any;
let localUpdate;
const Dummy = () => {
const [update] = useUpdate('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
meta: { key: 'value' },
});
localUpdate = update;
return <span />;
};
render(
<CoreAdminContext
dataProvider={dataProvider}
queryClient={queryClient}
>
<Dummy />
</CoreAdminContext>
);
localUpdate();
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
meta: { key: 'value' },
});
});
await waitFor(() => {
expect(
queryClient.getQueryData([
'foo',
'getOne',
{ id: '1', meta: { key: 'value' } },
])
).toEqual({
id: 1,
bar: 'baz',
});
});
});
});

describe('optimistic mutation mode', () => {
it('updates getOne query cache immediately and invalidates query when dataProvider promise resolves', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(
['foo', 'getOne', { id: '1', meta: undefined }],
{ id: 1, bar: 'bar' }
);
const dataProvider = {
update: jest.fn(() =>
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
),
} as any;
const queryClientSpy = jest.spyOn(
queryClient,
'invalidateQueries'
);
let localUpdate;
const Dummy = () => {
const [update] = useUpdate(undefined, undefined, {
mutationMode: 'optimistic',
});
localUpdate = update;
return <span />;
};
render(
<CoreAdminContext
dataProvider={dataProvider}
queryClient={queryClient}
>
<Dummy />
</CoreAdminContext>
);
localUpdate('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
});
await waitFor(() => {
expect(
queryClient.getQueryData([
'foo',
'getOne',
{ id: '1', meta: undefined },
])
).toEqual({
id: 1,
bar: 'baz',
});
});
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
});
});
await waitFor(() => {
expect(queryClientSpy).toHaveBeenCalledWith({
queryKey: [
'foo',
'getOne',
{ id: '1', meta: undefined },
],
});
});
});

it('updates getOne query cache immediately and invalidates query when dataProvider promise resolves with meta', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(
['foo', 'getOne', { id: '1', meta: { key: 'value' } }],
{ id: 1, bar: 'bar' }
);
const dataProvider = {
update: jest.fn(() =>
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
),
} as any;
const queryClientSpy = jest.spyOn(
queryClient,
'invalidateQueries'
);
let localUpdate;
const Dummy = () => {
const [update] = useUpdate(undefined, undefined, {
mutationMode: 'optimistic',
});
localUpdate = update;
return <span />;
};
render(
<CoreAdminContext
dataProvider={dataProvider}
queryClient={queryClient}
>
<Dummy />
</CoreAdminContext>
);
localUpdate('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
meta: { key: 'value' },
});
await waitFor(() => {
expect(
queryClient.getQueryData([
'foo',
'getOne',
{ id: '1', meta: { key: 'value' } },
])
).toEqual({
id: 1,
bar: 'baz',
});
});
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
meta: { key: 'value' },
});
});
await waitFor(() => {
expect(queryClientSpy).toHaveBeenCalledWith({
queryKey: [
'foo',
'getOne',
{ id: '1', meta: { key: 'value' } },
],
});
});
});

it('updates getOne query cache immediately and invalidates query when dataProvider promise resolves with meta at hook time', async () => {
const queryClient = new QueryClient();
queryClient.setQueryData(
['foo', 'getOne', { id: '1', meta: { key: 'value' } }],
{ id: 1, bar: 'bar' }
);
const dataProvider = {
update: jest.fn(() =>
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
),
} as any;
const queryClientSpy = jest.spyOn(
queryClient,
'invalidateQueries'
);
let localUpdate;
const Dummy = () => {
const [update] = useUpdate(
'foo',
{
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
meta: { key: 'value' },
},
{
mutationMode: 'optimistic',
}
);
localUpdate = update;
return <span />;
};
render(
<CoreAdminContext
dataProvider={dataProvider}
queryClient={queryClient}
>
<Dummy />
</CoreAdminContext>
);
localUpdate();
await waitFor(() => {
expect(
queryClient.getQueryData([
'foo',
'getOne',
{ id: '1', meta: { key: 'value' } },
])
).toEqual({
id: 1,
bar: 'baz',
});
});
await waitFor(() => {
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
id: 1,
data: { bar: 'baz' },
previousData: { id: 1, bar: 'bar' },
meta: { key: 'value' },
});
});
await waitFor(() => {
expect(queryClientSpy).toHaveBeenCalledWith({
queryKey: [
'foo',
'getOne',
{ id: '1', meta: { key: 'value' } },
],
});
});
});
});
});

describe('middlewares', () => {
it('when pessimistic, it accepts middlewares and displays result and success side effects when dataProvider promise resolves', async () => {
render(<WithMiddlewaresSuccessPessimistic timeout={10} />);
Expand Down
3 changes: 2 additions & 1 deletion packages/ra-core/src/dataProvider/useUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,13 @@ export const useUpdate = <RecordType extends RaRecord = any, ErrorType = Error>(
const {
resource: callTimeResource = resource,
id: callTimeId = id,
meta: callTimeMeta = meta,
} = variables;
updateCache({
resource: callTimeResource,
id: callTimeId,
data,
meta: mutationOptions.meta ?? paramsRef.current.meta,
meta: callTimeMeta,
});

if (
Expand Down

0 comments on commit fef8540

Please sign in to comment.