('pessimistic');
+ const [updateMany, { isPending }] = useUpdateMany(
+ 'posts',
+ {
+ ids: [1, 2],
+ data: { id: undefined, title: 'world' },
+ },
+ { mutationMode }
+ );
+ const handleClick = () => {
+ updateMany();
+ };
+ return (
+ <>
+ {JSON.stringify(data)}
+
+
+
+
+
+ >
+ );
+};
+
+export const Params = ({ dataProvider }: { dataProvider?: DataProvider }) => {
+ const data = [
+ { id: 1, title: 'foo' },
+ { id: 2, title: 'bar' },
+ ];
+ const defaultDataProvider = {
+ getList: async () => ({ data, total: 2 }),
+ updateMany: () => new Promise(() => {}), // never resolve to see only optimistic update
+ } as any;
+ return (
+
+
+
+ );
+};
+
+const ParamsCore = () => {
+ const { data } = useGetList('posts');
+ const [params, setParams] = React.useState({});
+ const [updateMany, { isPending }] = useUpdateMany(
+ 'posts',
+ {
+ ids: [1, 2],
+ data: { id: undefined, title: 'world' },
+ meta: params.meta,
+ },
+ { mutationMode: 'optimistic' }
+ );
+ const handleClick = () => {
+ updateMany();
+ };
+ return (
+ <>
+ {JSON.stringify(data)}
+
+
+
+
+
+ >
+ );
+};
diff --git a/packages/ra-core/src/dataProvider/useUpdateMany.ts b/packages/ra-core/src/dataProvider/useUpdateMany.ts
index 1f261467d25..0051e37fbc0 100644
--- a/packages/ra-core/src/dataProvider/useUpdateMany.ts
+++ b/packages/ra-core/src/dataProvider/useUpdateMany.ts
@@ -1,4 +1,4 @@
-import { useMemo, useRef } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import {
useMutation,
useQueryClient,
@@ -95,9 +95,18 @@ export const useUpdateMany = <
getMutateWithMiddlewares,
...mutationOptions
} = options;
+
const mode = useRef(mutationMode);
+ useEffect(() => {
+ mode.current = mutationMode;
+ }, [mutationMode]);
+
const paramsRef =
useRef>>>(params);
+ useEffect(() => {
+ paramsRef.current = params;
+ }, [params]);
+
const snapshot = useRef([]);
// Ref that stores the mutation with middlewares to avoid losing them if the calling component is unmounted
const mutateWithMiddlewares = useRef(dataProvider.updateMany);
From 2797c03c1f634e43b0c9f1b35a80d6a5d9e067d5 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Mon, 25 Aug 2025 09:39:54 +0200
Subject: [PATCH 2/3] Remove logs from useDelete stories and tests
---
.../dataProvider/useDelete.optimistic.stories.tsx | 12 ++++--------
.../useDelete.pessimistic.stories.tsx | 12 ++++--------
.../ra-core/src/dataProvider/useDelete.spec.tsx | 15 ++-------------
.../src/dataProvider/useDelete.stories.tsx | 12 ++++--------
.../dataProvider/useDelete.undoable.stories.tsx | 12 ++++--------
5 files changed, 18 insertions(+), 45 deletions(-)
diff --git a/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx
index 5e6f26d417a..06f16745f53 100644
--- a/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx
+++ b/packages/ra-core/src/dataProvider/useDelete.optimistic.stories.tsx
@@ -14,15 +14,13 @@ export const SuccessCase = () => {
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: (resource, params) => {
- console.log('delete', resource, params);
+ delete: (_, params) => {
return new Promise(resolve => {
setTimeout(() => {
const index = posts.findIndex(p => p.id === params.id);
@@ -82,15 +80,13 @@ export const ErrorCase = () => {
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: (resource, params) => {
- console.log('delete', resource, params);
+ delete: () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('something went wrong'));
diff --git a/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx
index aff6bb90689..29dd18d1d89 100644
--- a/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx
+++ b/packages/ra-core/src/dataProvider/useDelete.pessimistic.stories.tsx
@@ -16,15 +16,13 @@ export const SuccessCase = () => {
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: (resource, params) => {
- console.log('delete', resource, params);
+ delete: (_, params) => {
return new Promise(resolve => {
setTimeout(() => {
const index = posts.findIndex(p => p.id === params.id);
@@ -181,15 +179,13 @@ export const ErrorCase = () => {
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: (resource, params) => {
- console.log('delete', resource, params);
+ delete: () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('something went wrong'));
diff --git a/packages/ra-core/src/dataProvider/useDelete.spec.tsx b/packages/ra-core/src/dataProvider/useDelete.spec.tsx
index 7a1ccbfb5e9..3e07f66091f 100644
--- a/packages/ra-core/src/dataProvider/useDelete.spec.tsx
+++ b/packages/ra-core/src/dataProvider/useDelete.spec.tsx
@@ -77,7 +77,6 @@ describe('useDelete', () => {
});
it('uses the latest declaration time mutationMode', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
// This story uses the pessimistic mode by default
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
@@ -99,21 +98,18 @@ describe('useDelete', () => {
});
it('uses the latest declaration time params', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
const posts = [
{ id: 1, title: 'Hello' },
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: jest.fn((resource, params) => {
- console.log('delete', resource, params);
+ delete: jest.fn((_, params) => {
return new Promise(resolve => {
setTimeout(() => {
const index = posts.findIndex(p => p.id === params.id);
@@ -384,7 +380,6 @@ describe('useDelete', () => {
describe('mutationMode', () => {
it('when pessimistic, displays result and success side effects when dataProvider promise resolves', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
render();
screen.getByText('Delete first post').click();
await waitFor(() => {
@@ -401,7 +396,6 @@ describe('useDelete', () => {
expect(screen.queryByText('World')).not.toBeNull();
});
it('when pessimistic, displays error and error side effects when dataProvider promise rejects', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
render();
screen.getByText('Delete first post').click();
@@ -450,7 +444,6 @@ describe('useDelete', () => {
).not.toBeNull();
});
it('when optimistic, displays result and success side effects right away', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
screen.getByText('Delete first post').click();
@@ -468,7 +461,6 @@ describe('useDelete', () => {
});
});
it('when optimistic, displays error and error side effects when dataProvider promise rejects', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
@@ -490,7 +482,6 @@ describe('useDelete', () => {
});
});
it('when undoable, displays result and success side effects right away and fetched on confirm', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
screen.getByText('Delete first post').click();
@@ -518,7 +509,6 @@ describe('useDelete', () => {
);
});
it('when undoable, displays result and success side effects right away and reverts on cancel', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
screen.getByText('Delete first post').click();
@@ -536,7 +526,6 @@ describe('useDelete', () => {
});
});
it('when undoable, displays result and success side effects right away and reverts on error', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
diff --git a/packages/ra-core/src/dataProvider/useDelete.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.stories.tsx
index 0cf86f7dc72..f52ee316a66 100644
--- a/packages/ra-core/src/dataProvider/useDelete.stories.tsx
+++ b/packages/ra-core/src/dataProvider/useDelete.stories.tsx
@@ -15,15 +15,13 @@ export const MutationMode = () => {
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: (resource, params) => {
- console.log('delete', resource, params);
+ delete: (_, params) => {
return new Promise(resolve => {
setTimeout(() => {
const index = posts.findIndex(p => p.id === params.id);
@@ -94,15 +92,13 @@ export const Params = ({ dataProvider }: { dataProvider?: DataProvider }) => {
{ id: 2, title: 'World' },
];
const defaultDataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: (resource, params) => {
- console.log('delete', resource, params);
+ delete: (_, params) => {
return new Promise(resolve => {
setTimeout(() => {
const index = posts.findIndex(p => p.id === params.id);
diff --git a/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx b/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx
index 6dfbdd72083..e99e9b52999 100644
--- a/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx
+++ b/packages/ra-core/src/dataProvider/useDelete.undoable.stories.tsx
@@ -15,15 +15,13 @@ export const SuccessCase = () => {
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: (resource, params) => {
- console.log('delete', resource, params);
+ delete: (_, params) => {
return new Promise(resolve => {
setTimeout(() => {
const index = posts.findIndex(p => p.id === params.id);
@@ -112,15 +110,13 @@ export const ErrorCase = () => {
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- delete: (resource, params) => {
- console.log('delete', resource, params);
+ delete: () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('something went wrong'));
From 0bab27aa9b5b944af8e27425b04daaccc2d90fd8 Mon Sep 17 00:00:00 2001
From: Gildas <1122076+djhi@users.noreply.github.com>
Date: Mon, 25 Aug 2025 09:41:11 +0200
Subject: [PATCH 3/3] Remove logs from useDeleteMany stories and tests
---
.../ra-core/src/dataProvider/useDeleteMany.spec.tsx | 8 ++------
.../src/dataProvider/useDeleteMany.stories.tsx | 12 ++++--------
2 files changed, 6 insertions(+), 14 deletions(-)
diff --git a/packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx b/packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx
index de1209bd8c8..ef4f5309022 100644
--- a/packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx
+++ b/packages/ra-core/src/dataProvider/useDeleteMany.spec.tsx
@@ -58,7 +58,6 @@ describe('useDeleteMany', () => {
});
it('uses the latest declaration time mutationMode', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
// This story uses the pessimistic mode by default
render();
await waitFor(() => new Promise(resolve => setTimeout(resolve, 0)));
@@ -80,21 +79,18 @@ describe('useDeleteMany', () => {
});
it('uses the latest declaration time params', async () => {
- jest.spyOn(console, 'log').mockImplementation(() => {});
let posts = [
{ id: 1, title: 'Hello' },
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- deleteMany: jest.fn((resource, params) => {
- console.log('deleteMany', resource, params);
+ deleteMany: jest.fn((_, params) => {
return new Promise(resolve => {
setTimeout(() => {
posts = posts.filter(
diff --git a/packages/ra-core/src/dataProvider/useDeleteMany.stories.tsx b/packages/ra-core/src/dataProvider/useDeleteMany.stories.tsx
index a350d7cb6f2..5bba02a1d57 100644
--- a/packages/ra-core/src/dataProvider/useDeleteMany.stories.tsx
+++ b/packages/ra-core/src/dataProvider/useDeleteMany.stories.tsx
@@ -15,15 +15,13 @@ export const MutationMode = () => {
{ id: 2, title: 'World' },
];
const dataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- deleteMany: (resource, params) => {
- console.log('delete', resource, params);
+ deleteMany: (_, params) => {
return new Promise(resolve => {
setTimeout(() => {
posts = posts.filter(post => !params.ids.includes(post.id));
@@ -92,15 +90,13 @@ export const Params = ({ dataProvider }: { dataProvider?: DataProvider }) => {
{ id: 2, title: 'World' },
];
const defaultDataProvider = {
- getList: (resource, params) => {
- console.log('getList', resource, params);
+ getList: () => {
return Promise.resolve({
data: posts,
total: posts.length,
});
},
- deleteMany: (resource, params) => {
- console.log('deleteMany', resource, params);
+ deleteMany: (_, params) => {
return new Promise(resolve => {
setTimeout(() => {
posts = posts.filter(post => !params.ids.includes(post.id));