-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Race condition in query cancellation #3045
Comments
I think this is exactly the problem. They both "use" the same query, but they do not both "observe" it. react-query doesn't differentiate between who initiated the fetch. For example, if you have one observer, and you trigger a fetch via a window focus, and then you unmount the observer because you go to a different page, we also cancel that fetch because there is no one interested in that data (if the signal was consumed). I think this is similar to what happens in your case:
yes, the problem could be a "race-condition", but with one solution would be to defer the query cancellation by a bit to see if a new subscriber mounts before actually cancelling it. This has also been pointed out by the react core team in a discussion about You've said that you only
what is the initialization phase please, and how can this happen on a page before the previous page unmounts? |
Yes, your analysis of why this happens is correct. I can also see the current difficulty in determining who initiated the ongoing fetch request (either the observing component being unmounted, or a
The initialization phase is just an Initialization state which is the first state the state machine enters when it is started. I'm attaching this state diagram to clarify the state machine's operation (simplified): https://stately.ai/viz/95727c7e-9e82-4b9c-a029-bbcb0d78c1d3 To be clear, the state machine interpreter (xstate) does make use of function SecondPage() {
const service = interpret(stateMachine) // the machine is "interpreted" immediately on this first render. The stateMachine blueprint is used to instantiate a ready-to-start service instance (memoized).
useLayoutEffect(() => {
service.start() // the machine is started and enters its first state - Initialization
// the fetch request is in flight now
}, []) I am not deeply familiar with React internal workings. Apparently, it first executes the layout effect from the SecondPage before cleaning up effects from the FirstPage (unmounted).
Deferring the query cancellation may not help here, since AFAIK |
right, so the machine uses
That is right. Have a look at this sandbox. You can see that the layoutEffect in component two runs before the cleanup of the useEffect in component one. If both were to use the same effects (both useEffect or both useLayoutEffect), the problem wouldn't happen. Now the question is who is doing it right / wrong 🤔 . I think we don't use layout effects because they will give a warning during server-side rendering. I also don't think layout effects are "meant" for data fetching, but I could be wrong. Maybe you can find out why the machine uses a layout effect? That being said, with react 18, we'll move away from fetching in an effect towards
beware the breaking changes (query keys need to be arrays). I am not sure how the timing of things will change with that, but maybe it'll solve the issue. on a different note, I think the preferred way of using react-query together with xstate is / will be via the
|
Also, if both xstate and react-query move towards |
I posted a related question in the xstate forum. |
As it stands, I think this is more an issue with xstate than react-query. If we all move towards |
Describe the bug
If the AbortSignal is consumed, a new fetch request initiated by
QueryClient.fetchQuery
gets cancelled when the previous component unmounts (where the query was used byuseQuery
).To Reproduce
Steps to reproduce the behavior:
Expected behavior
As the new component
SecondPage
mounts, a new request to the fruits endpoint is made and allowed to complete. It should not get cancelled due to the previous componentFirstPage
unmounting (FirstPage used the same Query by means ofuseQuery
) .Screenshots
Desktop (please complete the following information):
Additional context
For context:
The provided sandbox is a simplified simulation of a real-life app, where the SecondPage component is driven by a state machine (xstate). The state machine calls the
queryClient.fetchQuery
API during the initialization phase. When the query fails (due to it being aborted), the state machine waits 10 seconds before trying to initialize again. Thus aborting the query creates a needless 10 seconds delay before the component driven by that state machine is rendered.And why use the
fetchQuery
API instead ofuseQuery
? We need to use the imperativefetchQuery
in the state machine to fetch data at the right time (not on the first render of the component driven by the state machine).Other scenarios where both
useQuery
andfetchQuery
need to be used across the app are conceivable. The point is, this is not a state machine problem, but rather a problem of racey clean up of observers from an unmounted query having the effect of cancelling requests from the same query used in a freshly mounted component:this.retryer.cancel({ revert: true })
cancels the new request made fromQueryClient.fetchQuery
.The text was updated successfully, but these errors were encountered: