[BUG] React Query double fetching in Refine v5 caused by unintended signal access (v4 did not do this)
Describe the bug
Refine v5’s useList performs two HTTP requests in React 18 + Strict Mode in development, while refine v4 performs only one.
This is not caused by Strict Mode alone.
The root cause is that refine v5 accidentally opts into React Query abort/cancel semantics by reading the context.signal getter during queryFn execution via a spread operation:
const meta = {
...combinedMeta,
...prepareQueryContext(context), // <-- this spread triggers context.signal
};
Spreading an object with a signal getter invokes the getter, causing React Query to detect that signal is used. When signal is used, React Query treats the query as abortable, and in dev + Strict Mode it cancels the first run and issues a second fetch.
This behavior did not happen in refine v4 because v4 passed the signal getter lazily (meta.queryContext) without accessing it inside queryFn.
This explains why:
- v4 = 1 request
- v5 = 2 requests
- Patching out the spread = v5 goes back to 1 request
Steps To Reproduce
- Create two minimal refine apps (one using refine v4, one using refine v5).
My reproduction repo structure:
. ├── refine-v4 │ ├── src/App.jsx │ ├── index.html │ └── package.json └── refine-v5 ├── src/App.jsx ├── index.html └── package.json - Both apps use the same simple
<useList>component:const { data, isLoading } = useList({ resource: "posts" }); - Run both apps in React 18 with Strict Mode enabled (default Vite setup).
- Open DevTools → Network tab
- In refine v4, you will see one network request.
- In refine v5, you will see two network requests.
- Now patch refine v5’s compiled JS:
Replace:
With:const meta2 = { ...combinedMeta, ...prepareQueryContext(context) };const meta2 = { ...combinedMeta, queryKey: context.queryKey }; Object.defineProperty(meta2, "signal", { enumerable: true, get() { return context.signal; }, }); - Restart refine v5. The second request disappears.
Expected behavior
Refine v5 should behave like refine v4:
- One request in development + Strict Mode if the data provider does not explicitly opt in to abort controller semantics.
context.signalshould remain lazy and not be accessed insidequeryFnuntil a data provider actually uses it.
Packages
Refine v4:
yarn why @refinedev/core
└─ refine-v4-test@workspace:.
└─ @refinedev/core@npm:4.58.0 [53edc] (via npm:^4.49.0 [53edc])
Refine v5:
yarn why @refinedev/core
└─ refine-v5-test@workspace:.
└─ @refinedev/core@npm:5.0.6 [1e1cb] (via npm:^5.0.0 [1e1cb])
Additional Context
- This behavior is fully explained by the TanStack Query team here: https://github.com/TanStack/query/issues/3633
- Reading or destructuring
signalmarks the query as abortable. - In refine v5, the spread operator
...prepareQueryContext(context)invokes thesignalgetter, which implicitly accessescontext.signal. That is the only difference from refine v4. - Strict Mode double-render alone does not cause duplicate network requests unless the query is opt-in abortable.
- Two older refine issues referenced Strict Mode but did not identify the real cause: https://github.com/refinedev/refine/issues/3449 https://github.com/refinedev/refine/issues/2978
- A minimal and backwards-compatible fix is simply avoiding the spread of an object with
signalgetter properties, and instead defining the lazy getter directly onmeta, matching v4 behavior.
If needed, I can also provide a PR updating useList, useOne, useMany, etc., to ensure the signal getter is not invoked unless explicitly requested by the data provider.
Here is an example repo demonstrating the behavior: https://github.com/amerryma/refine-react-query-v4-v5-bug
Hello @amerryma, thanks for the detailed issue. Would you like to create a PR?
Interesting, it was a regression that was originally resolved here: https://github.com/refinedev/refine/pull/5851
We'll want to make sure we check out why the tests never caught this.
Hello @amerryma any updates?
Hello, I would like to work on this issue. I will begin implementing a fix and submit a PR once it’s ready.
Hello @amerryma any updates?
I have not had any time to work through this yet, it's pretty low priority for me because it only affects local dev.
This is preventing us from upgrading to Refine v5, as it breaks our permission checks in dev. For some reason, the double call to retrieve someone's abilities causes 401s / other errors (the first one works properly, also in non-dev), resulting in the app not being usable in dev, making it quite difficult to build new things/fix issues.