query: Mutations can not be started from an effect in Reacts StrictMode
Describe the bug
When firing a mutation from a useEffect on component creation the mutation get’s stuck in loading state, despite the underlying request finishing. This only seems to happen in Reacts StrictMode.
If you naively do this you won’t notice the bug, because this effect will fire twice in StrictMode and therefore “unstuck” the mutation when the request is sent the second time.
useEffect(() => {
if (isIdle) mutate()
}, [isIdle, mutate])
That’s why the codesandbox example has a ref that keeps track on if the mutation has fired, to ensure it only runs once.
The reason we’re doing this in the first place is because we need to fire several mutations and keep track of the loading states individually, so we use components to create several mutation instances and fire the mutations on effect instead of in the original event handler.
Your minimal, reproducible example
https://codesandbox.io/s/mutation-with-suspense-onsuccess-forked-lkmo6v?file=/src/Test.tsx
Steps to reproduce
- Go to attached codesandbox
- Observe that the two loading states differ (reload if you have to)
Expected behavior
I expect react-query’s loading state to have the same value as the manually coded loading state
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- OS: MacOS
- Browser: Chrome
Tanstack Query adapter
react-query
TanStack Query version
v4.28
TypeScript version
v5.0.4
Additional context
No response
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 1
- Comments: 17
My use case is this:
The problem is that, in the above,
mutationFnis called twice.We could have used
useQueryhere, but the act of signing in is a mutation, so it doesn’t make much sense to useuseQueryhere. We also don’t want to deal with automatic retries and automatic refetching, i.e. we want the “mutation” behavior.Using
useRefto only callmutation.mutate()does not work, as per this comment: https://github.com/TanStack/query/issues/5341#issuecomment-1527727996We oftentimes have to implement pages/routes whose sole purpose is to perform some action after loading, and then redirect user to another page. OAuth callbacks for public clients is one example. Payment processing is another example.
…and this is the solution:
Currently we work around it like this:Edit: Found a simpler workaround:
…but I couldn’t expect junior devs to always implement this pattern correctly, therefore I wish there is an easier way to do it with React Query.
I would love to put it in event handlers too! But is there any event suitable for implementing such callback page? I would like to avoid something like this if possible:
Any suggestion is appreciated.
@TkDodo I ran into the exact same issue: after I fire the mutation once, the
statusis stuck inpending. The underlying mutation firesonSuccess, so why does it not transition to thesuccessstate? This is a bug.In our case we need to POST once for a given combination of data. The
useEffectdependencies make sure we only fire once for that combination. We use theuseRefworkaround to avoid firing twice in react-strict mode. Any workaround makes the code even more unreadable.As to your workarounds:
Impossible if the underlying API call is not idempotent. Like a
POSTthat is working on the first call, and fails on the second call.Why should I remove strict mode? It is making the code more robust in a lot of ways.
Please explain how I would create the same behavior without useEffect?
Great, so now I have to re-create react-query’s very helpful state management, debugging and tooling? Not an option.
Also note that in the official react PR that added StrictMode, the
useRefworkaround was mentioned for “strictly once” behavior: https://github.com/reactwg/react-18/discussions/18Please, reopen this issue and fix this. Not every mutation is called from an event handler.
sorry, I’m not going to invest time into issues caused by workarounds (refs) to make StrictMode not do something in dev mode that it’s intended to do. You can:
What is the business requirement ?
Anyways, there is no change in production. Let it fire twice in dev mode, it’s fine in prod 🤷
Yeah but your modified example only works because it’s firing twice. The first call gets stuck on loading, and then the second one unstucks it. Do you mean that you do not consider this a bug?
Yeah I would definitely prefer to run it in the event handler, but for our use case there is no way to do this with react-query AFAICT.
We want to fire an unknown amount of mutations with the same useMutation hook from our event handler, and then track each loading state individually. In some other issue thread I found your reply saying a solution was to run the mutation in the leaf component and trigger it on effect, so I figured that would be a reasonable way forward.
Is there any other pattern we could use here?