apollo-client: Not able to execute `useLazyQuery` multiple times with different variables
I’m not 100% sure whether this is a valid use case for useLazyQuery()
, but I still think this new behaviour is not intended because of the following reasons:
- The behaviour has changed between v3.5.10 and v3.6.x.
- The new behaviour does not seem to be right, since the promise that gets resolved after executing a query with some variables corresponds to the response from another query executed with different variables.
Intended outcome:
When executing the same lazy query twice on the same tick with different variables, Apollo should execute two network requests each one with the corresponding variables and return the correct results
const [fetch] = useLazyQuery(CountriesList);
useEffect(() => {
Promise.all([
fetch({variables: {continent: "EU"}}),
fetch({variables: {continent: "NA"}})
]).then(([result1, result2]) => {
// result1 should contain the results of executing CountriesList query with continent=EU
// result2 should contain the results of executing CountriesList query with continent=NA
});
}, [fetch]);
Actual outcome:
When executing the same lazy query twice on the same tick with different variables, Apollo instead executes a single network request and returns the same results for the two requests.
So, on the code snipped pasted above, both result1
and result2
contains the results of executing CountriesList query with continent=NA.
How to reproduce the issue:
- CodeSandbox with
apollo@3.6.4
(issue reproducible): https://codesandbox.io/s/winter-sea-1i2wpn?file=/src/App.js - CodeSandbox with
apollo@3.5.10
(issue not appearing): https://codesandbox.io/s/silly-resonance-c8nj8i
The expected behaviour is the two fetch
calls on L27 and L28 to return different results, and therefore the UI should show 2 columns with different sets of countries.
Versions
@apollo/client >= 3.6.0
(the issue is also present in the currently latest available alpha (v3.7.0-alpha.7
).
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 31
- Comments: 21 (6 by maintainers)
Got this issue as well. Another work around would be
useApolloClient
directly.Hey @rafeca 👋
Thanks so much for your patience on this issue! We just released v3.7.11 which now corrects the behavior. If you kick off multiple requests in parallel, each promise will now resolve with the correct data. The hook will always reflect the last execution of the query. Thanks for reporting the issue!
@rafeca thanks so much for this detail. This is extremely helpful understanding the context surrounding this issue.
I’ll try and respond to bits of this over time, but I’d love to take this back to the Apollo team and throw some ideas/questions around. FWIW, I agree that there are a few quirky things with
useLazyQuery
(like theloading
flag in the resolved promise you pointed out). We’ll discuss and figure out if we deem this buggy or not.Thanks again!
We have the same problem, downgraded to 3.5.10 did work but is pretty disappointing.
How does it come that this bug hasn’t been fixed since may? We’re at 3.7.0 now. Executing a lazy query seems to be a pretty much basic and widely used functionality for me. Just curious.
Ran into this issue in 2023. ended up making something similar to the
client.query
example above by @jerelmillerFaced the same issue to with version
"@apollo/client@^3.6.9"
had to downgrade to3.5.9
@benjamn have you had any chance to look at this issue? It would be good to hear your thoughts on this (specially whether this is considered a bug or intended behaviour for us to plan ahead).
Hey @jerelmiller! thanks a lot for taking the time into writing such a detailed response.
I filled the issue because this is a change of behaviour in Apollo v3.6.0 which in my opinion is buggy now (resolving the returned promise with a response from a different request is wrong). I agree with you that there are better ways to achieve the example code that I pasted there (and in fact I’m working in limiting the way people use
useLazyQuery
in my company, the problem is that I’m not going to be able to change all existing code that we already have).So this issue was initially filled to know whether the Apollo team thinks this change of behaviour is indeed an issue or on the other hand it’s intended. We wanted to decide what to do based on what you folks tell us, since this change is preventing us from upgrading to Apollo v3.6 (we have a huge codebase with hundreds of
useLazyQuery
usages relying on the previous correct behaviour that we cannot easily change at this moment). In the last months we’ve decided to start working on our own versions ofuseLazyQuery
anduseMutation
to have more control over their behaviour and rely less on the potential changes of undocumented behaviour.In that scenario, I expect
data
,loading
anderror
to contain the values of the last started execution (no matter whether it started in the same tick of other executions).100% agree, but as I mention we have a lot of existing code already using
useLazyQuery
and depending on the previous behaviour.Yes, this is what we’re planning to do to be able to upgrade to Apollo v3.6 without having to change all product code. I’m going to create a custom version of
useLazyQuery
with the previous behaviour to unblock the upgrade. Once we’re upgraded my plan is to ask product teams to change their code to not rely on the promise results to eventually be able to limit the API surface of our custom hooks.This was just a minimal repro silly example 😄 I can come back with more realistic examples where lazy queries are executed multiple times if you want (I work in an infra team so I’m not 100% familiar with all the patterns that the product code uses). I know that this pattern is widely used by our list components to do optimistic fetching on pagination.
Related to all this, but also a bit offtopic to this issue, I think there are a few flaws in the APIs of the
useLazyQuery
(anduseMutation
) hooks:data
,error
andloading
: first, having bothloading
and the Promise is unnecessary, we just need one loading indicator. Same witherror
and promise rejection (the promise currently gets rejected only when there are network errors but this is undocumented behaviour and it has also changed in the past and may change in the future as well).As I mentioned before, to solve these flaws our end goal is to have our own custom capped down versions of
useLazyQuery()
/useMutation()
that only returns data on the hook (the callback promise resolves tovoid
) and only accepts variables on the callback.Are these flaws something that the Apollo team sees as a problem?
not sure but https://www.apollographql.com/docs/react/api/react/hooks/#refetch isn’t actually refetch the thing that should be used?
I’m seeing this issue as well.
useLazyQuery
writes the request data to a ref, so insideuseQuery.asyncUpdate
all the calls have the same data (the last request).https://github.com/apollographql/apollo-client/blob/a7970cf3e06a44b695350d6dd7326a3f087294c8/src/react/hooks/useLazyQuery.ts#L74-L82
Hey all 👋! Thanks so much for your patience with this issue. I’d like to best understand the expected behavior here and see if we can come to a resolution. I can see both sides of the argument in this case where this could either be viewed as a bug or intended behavior.
Something to note is that
useLazyQuery
returns adata
andloading
property in the 2nd item of the tuple. This allows you to render you component with that data, without you needing to manage that state yourself. From our docs:Note here how we use
data
to render the component. In this case, you can just execute the query without needing toawait
the promise or manage the state yourself.I can definitely see the merit in wanting to execute the query in the same tick with multiple arguments. My question is: what would you expect the values of
data
,loading
, anderror
to be when you executed this bit of code?We have to be careful here since this would result in a race condition for these properties.
I generally view
useLazyQuery
as a way to delay execution of a single query (query in this case meaning a single chunk of data from your server including its lifecycle) rather than a means to arbitrarily query data. In this vein, I thinkuseLazyQuery
is working as expected.For your particular example, you might be better off using
client.query
anyways directly as it doesn’t come with the lifecycle baggage ofuseLazyQuery
, nor will it unnecessarily rerender your component as you’re managing the lifecycle yourself anyways:And if you want something to feel more React-y, you can always create a custom hook to wrap this particular behavior:
Given your specific example, I’m curious if you’d even need
useLazyQuery
as I see its executing inuseEffect
to fetch immediately (though I take this with a grain of salt given that you might be using this to demonstrate the issue). Instead, would it make more sense to split this up between 2useQuery
oruseLazyQuery
hooks? I think you get the same benefit but thedata
/loading
/error
properties are much more predictable:In this case, this gives you the benefit of just using the
result
on the tuple instead of needing to manage state yourself.All this being said, I’d love to understand how you view the expected behavior here. It’s possible there is a good case for making a single
useLazyQuery
hook work with multiple calls to its fetch function in the same tick. I’d love to hear more of your thoughts. If this is the case, I think we need to flush out the expected behavior for the hook lifecycle to ensure it remains working as you’d expect.