Skip to content

Commit fef8540

Browse files
committed
Fix useUpdate ignores meta when populating the query cache in pessimistic mode
1 parent dabb850 commit fef8540

File tree

2 files changed

+369
-1
lines changed

2 files changed

+369
-1
lines changed

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

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,374 @@ describe('useUpdate', () => {
510510
});
511511
});
512512
});
513+
describe('pessimistic mutation mode', () => {
514+
it('updates getOne query cache when dataProvider promise resolves', async () => {
515+
const queryClient = new QueryClient();
516+
queryClient.setQueryData(
517+
['foo', 'getOne', { id: '1', meta: undefined }],
518+
{ id: 1, bar: 'bar' }
519+
);
520+
const dataProvider = {
521+
update: jest.fn(() =>
522+
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
523+
),
524+
} as any;
525+
let localUpdate;
526+
const Dummy = () => {
527+
const [update] = useUpdate();
528+
localUpdate = update;
529+
return <span />;
530+
};
531+
render(
532+
<CoreAdminContext
533+
dataProvider={dataProvider}
534+
queryClient={queryClient}
535+
>
536+
<Dummy />
537+
</CoreAdminContext>
538+
);
539+
localUpdate('foo', {
540+
id: 1,
541+
data: { bar: 'baz' },
542+
previousData: { id: 1, bar: 'bar' },
543+
});
544+
await waitFor(() => {
545+
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
546+
id: 1,
547+
data: { bar: 'baz' },
548+
previousData: { id: 1, bar: 'bar' },
549+
});
550+
});
551+
await waitFor(() => {
552+
expect(
553+
queryClient.getQueryData([
554+
'foo',
555+
'getOne',
556+
{ id: '1', meta: undefined },
557+
])
558+
).toEqual({
559+
id: 1,
560+
bar: 'baz',
561+
});
562+
});
563+
});
564+
565+
it('updates getOne query cache when dataProvider promise resolves with meta', async () => {
566+
const queryClient = new QueryClient();
567+
queryClient.setQueryData(
568+
['foo', 'getOne', { id: '1', meta: { key: 'value' } }],
569+
{ id: 1, bar: 'bar' }
570+
);
571+
const dataProvider = {
572+
update: jest.fn(() =>
573+
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
574+
),
575+
} as any;
576+
let localUpdate;
577+
const Dummy = () => {
578+
const [update] = useUpdate();
579+
localUpdate = update;
580+
return <span />;
581+
};
582+
render(
583+
<CoreAdminContext
584+
dataProvider={dataProvider}
585+
queryClient={queryClient}
586+
>
587+
<Dummy />
588+
</CoreAdminContext>
589+
);
590+
localUpdate('foo', {
591+
id: 1,
592+
data: { bar: 'baz' },
593+
previousData: { id: 1, bar: 'bar' },
594+
meta: { key: 'value' },
595+
});
596+
await waitFor(() => {
597+
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
598+
id: 1,
599+
data: { bar: 'baz' },
600+
previousData: { id: 1, bar: 'bar' },
601+
meta: { key: 'value' },
602+
});
603+
});
604+
await waitFor(() => {
605+
expect(
606+
queryClient.getQueryData([
607+
'foo',
608+
'getOne',
609+
{ id: '1', meta: { key: 'value' } },
610+
])
611+
).toEqual({
612+
id: 1,
613+
bar: 'baz',
614+
});
615+
});
616+
});
617+
618+
it('updates getOne query cache when dataProvider promise resolves with meta at hook time', async () => {
619+
const queryClient = new QueryClient();
620+
queryClient.setQueryData(
621+
['foo', 'getOne', { id: '1', meta: { key: 'value' } }],
622+
{ id: 1, bar: 'bar' }
623+
);
624+
const dataProvider = {
625+
update: jest.fn(() =>
626+
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
627+
),
628+
} as any;
629+
let localUpdate;
630+
const Dummy = () => {
631+
const [update] = useUpdate('foo', {
632+
id: 1,
633+
data: { bar: 'baz' },
634+
previousData: { id: 1, bar: 'bar' },
635+
meta: { key: 'value' },
636+
});
637+
localUpdate = update;
638+
return <span />;
639+
};
640+
render(
641+
<CoreAdminContext
642+
dataProvider={dataProvider}
643+
queryClient={queryClient}
644+
>
645+
<Dummy />
646+
</CoreAdminContext>
647+
);
648+
localUpdate();
649+
await waitFor(() => {
650+
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
651+
id: 1,
652+
data: { bar: 'baz' },
653+
previousData: { id: 1, bar: 'bar' },
654+
meta: { key: 'value' },
655+
});
656+
});
657+
await waitFor(() => {
658+
expect(
659+
queryClient.getQueryData([
660+
'foo',
661+
'getOne',
662+
{ id: '1', meta: { key: 'value' } },
663+
])
664+
).toEqual({
665+
id: 1,
666+
bar: 'baz',
667+
});
668+
});
669+
});
670+
});
671+
672+
describe('optimistic mutation mode', () => {
673+
it('updates getOne query cache immediately and invalidates query when dataProvider promise resolves', async () => {
674+
const queryClient = new QueryClient();
675+
queryClient.setQueryData(
676+
['foo', 'getOne', { id: '1', meta: undefined }],
677+
{ id: 1, bar: 'bar' }
678+
);
679+
const dataProvider = {
680+
update: jest.fn(() =>
681+
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
682+
),
683+
} as any;
684+
const queryClientSpy = jest.spyOn(
685+
queryClient,
686+
'invalidateQueries'
687+
);
688+
let localUpdate;
689+
const Dummy = () => {
690+
const [update] = useUpdate(undefined, undefined, {
691+
mutationMode: 'optimistic',
692+
});
693+
localUpdate = update;
694+
return <span />;
695+
};
696+
render(
697+
<CoreAdminContext
698+
dataProvider={dataProvider}
699+
queryClient={queryClient}
700+
>
701+
<Dummy />
702+
</CoreAdminContext>
703+
);
704+
localUpdate('foo', {
705+
id: 1,
706+
data: { bar: 'baz' },
707+
previousData: { id: 1, bar: 'bar' },
708+
});
709+
await waitFor(() => {
710+
expect(
711+
queryClient.getQueryData([
712+
'foo',
713+
'getOne',
714+
{ id: '1', meta: undefined },
715+
])
716+
).toEqual({
717+
id: 1,
718+
bar: 'baz',
719+
});
720+
});
721+
await waitFor(() => {
722+
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
723+
id: 1,
724+
data: { bar: 'baz' },
725+
previousData: { id: 1, bar: 'bar' },
726+
});
727+
});
728+
await waitFor(() => {
729+
expect(queryClientSpy).toHaveBeenCalledWith({
730+
queryKey: [
731+
'foo',
732+
'getOne',
733+
{ id: '1', meta: undefined },
734+
],
735+
});
736+
});
737+
});
738+
739+
it('updates getOne query cache immediately and invalidates query when dataProvider promise resolves with meta', async () => {
740+
const queryClient = new QueryClient();
741+
queryClient.setQueryData(
742+
['foo', 'getOne', { id: '1', meta: { key: 'value' } }],
743+
{ id: 1, bar: 'bar' }
744+
);
745+
const dataProvider = {
746+
update: jest.fn(() =>
747+
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
748+
),
749+
} as any;
750+
const queryClientSpy = jest.spyOn(
751+
queryClient,
752+
'invalidateQueries'
753+
);
754+
let localUpdate;
755+
const Dummy = () => {
756+
const [update] = useUpdate(undefined, undefined, {
757+
mutationMode: 'optimistic',
758+
});
759+
localUpdate = update;
760+
return <span />;
761+
};
762+
render(
763+
<CoreAdminContext
764+
dataProvider={dataProvider}
765+
queryClient={queryClient}
766+
>
767+
<Dummy />
768+
</CoreAdminContext>
769+
);
770+
localUpdate('foo', {
771+
id: 1,
772+
data: { bar: 'baz' },
773+
previousData: { id: 1, bar: 'bar' },
774+
meta: { key: 'value' },
775+
});
776+
await waitFor(() => {
777+
expect(
778+
queryClient.getQueryData([
779+
'foo',
780+
'getOne',
781+
{ id: '1', meta: { key: 'value' } },
782+
])
783+
).toEqual({
784+
id: 1,
785+
bar: 'baz',
786+
});
787+
});
788+
await waitFor(() => {
789+
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
790+
id: 1,
791+
data: { bar: 'baz' },
792+
previousData: { id: 1, bar: 'bar' },
793+
meta: { key: 'value' },
794+
});
795+
});
796+
await waitFor(() => {
797+
expect(queryClientSpy).toHaveBeenCalledWith({
798+
queryKey: [
799+
'foo',
800+
'getOne',
801+
{ id: '1', meta: { key: 'value' } },
802+
],
803+
});
804+
});
805+
});
806+
807+
it('updates getOne query cache immediately and invalidates query when dataProvider promise resolves with meta at hook time', async () => {
808+
const queryClient = new QueryClient();
809+
queryClient.setQueryData(
810+
['foo', 'getOne', { id: '1', meta: { key: 'value' } }],
811+
{ id: 1, bar: 'bar' }
812+
);
813+
const dataProvider = {
814+
update: jest.fn(() =>
815+
Promise.resolve({ data: { id: 1, bar: 'baz' } } as any)
816+
),
817+
} as any;
818+
const queryClientSpy = jest.spyOn(
819+
queryClient,
820+
'invalidateQueries'
821+
);
822+
let localUpdate;
823+
const Dummy = () => {
824+
const [update] = useUpdate(
825+
'foo',
826+
{
827+
id: 1,
828+
data: { bar: 'baz' },
829+
previousData: { id: 1, bar: 'bar' },
830+
meta: { key: 'value' },
831+
},
832+
{
833+
mutationMode: 'optimistic',
834+
}
835+
);
836+
localUpdate = update;
837+
return <span />;
838+
};
839+
render(
840+
<CoreAdminContext
841+
dataProvider={dataProvider}
842+
queryClient={queryClient}
843+
>
844+
<Dummy />
845+
</CoreAdminContext>
846+
);
847+
localUpdate();
848+
await waitFor(() => {
849+
expect(
850+
queryClient.getQueryData([
851+
'foo',
852+
'getOne',
853+
{ id: '1', meta: { key: 'value' } },
854+
])
855+
).toEqual({
856+
id: 1,
857+
bar: 'baz',
858+
});
859+
});
860+
await waitFor(() => {
861+
expect(dataProvider.update).toHaveBeenCalledWith('foo', {
862+
id: 1,
863+
data: { bar: 'baz' },
864+
previousData: { id: 1, bar: 'bar' },
865+
meta: { key: 'value' },
866+
});
867+
});
868+
await waitFor(() => {
869+
expect(queryClientSpy).toHaveBeenCalledWith({
870+
queryKey: [
871+
'foo',
872+
'getOne',
873+
{ id: '1', meta: { key: 'value' } },
874+
],
875+
});
876+
});
877+
});
878+
});
513879
});
880+
514881
describe('middlewares', () => {
515882
it('when pessimistic, it accepts middlewares and displays result and success side effects when dataProvider promise resolves', async () => {
516883
render(<WithMiddlewaresSuccessPessimistic timeout={10} />);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,12 +269,13 @@ export const useUpdate = <RecordType extends RaRecord = any, ErrorType = Error>(
269269
const {
270270
resource: callTimeResource = resource,
271271
id: callTimeId = id,
272+
meta: callTimeMeta = meta,
272273
} = variables;
273274
updateCache({
274275
resource: callTimeResource,
275276
id: callTimeId,
276277
data,
277-
meta: mutationOptions.meta ?? paramsRef.current.meta,
278+
meta: callTimeMeta,
278279
});
279280

280281
if (

0 commit comments

Comments
 (0)