Some core lazy querying functionality through useLazyQuery
seems a bit off.
Specifically, when a lazy query (through a useLazyQuery
hook) returns an error, then Apollo seems to start endlessly querying the server. The same thing happens if you give it a fetchPolicy
of network-only
(or anything that doesn't search the cache first).
Related: https://github.com/apollographql/react-apollo/issues/4044
At the same time, if the lazy query function (returned by useLazyQuery
) gets called more than 1 times, then no callbacks are fired. The onCompleted
doesn't fire again. The onCompleted
fires on the 1st successful run of the "query" function and then consecutive invocations of the lazy query function, don't trigger onCompleted
callback (they should, since you can't have conditional business logic based on whether the data is cached or not!)
Related: https://github.com/apollographql/react-apollo/issues/4000
Funnily enough, using client.query()
works fine. All this used to work perfectly during the beta (I think I've verified that something occurred after @apollo/[email protected]
), but now it doesn't.
UPDATE: I've added a codesandbox that reproduces it
Intended outcome:
network-only
fetch policy shouldn't trigger endless API querying on a lazy queryonCompleted
should fire every time a lazy query gets successfully executedActual outcome:
network-only
fetch policy start an endless loop of requestsonCompeted
gets fired at most once regardless of the number of invocationsHow to reproduce the issue:
fetchPolicy
to network-only
or force an API error on a lazy queryonCompleted
callback.Versions
System:
OS: macOS 10.15.2
Binaries:
Node: 12.18.0 - /usr/local/bin/node
Yarn: 1.19.0 - /usr/local/bin/yarn
npm: 6.14.4 - /usr/local/bin/npm
Browsers:
Chrome: 83.0.4103.116
Firefox: 72.0.2
Safari: 13.0.4
npmPackages:
@apollo/client: ^3.0.2 => 3.0.2
apollo-link-error: ^1.1.12 => 1.1.13
@benjamn Take a look here and observe the console
https://codesandbox.io/s/apollo-reference-equality-1exes?file=/src/App.js
While fetchPolicy
is network-only
React re-renders endlessly. If you make it cache-first
things are ok again. Same thing would happen if the query errored.
Also observe how the ON COMPLETED
text gets only logged once regardless of how many times the query finishes successfully
I noticed that even if I change the variables
after one query execution, the query automatically executes and fires its onCompleted
(although it's lazy, so it shouldn't).
Related to https://github.com/apollographql/react-apollo/issues/3729
In general, this seems like a bug that may be unrelated to lazy querying, but I feel it's something on the "core" functionality of querying since it manifests in many different ways
I'm using 3.0.0-beta.43
since February and everything worked there just fine. Tried an upgrade to stable v3 and encountered this issue.
I've noticed that this is also happening with a regular useQuery
hook when an onCompleted
function is specified (even if its a noop) and the fetchPolicy
is set to anything except cache-first
or cache-only
I've noticed that this is also happening with a regular
useQuery
hook when anonCompleted
function is specified (even if its a noop) and thefetchPolicy
is set to anything exceptcache-first
orcache-only
Yeah I also mentioned that I feel that this is not necessarily tied only to lazy queries. Will update description
Same problem. Endless request loop when using onCompleted in one of my applications after upgrading. Might go back to apollo-boost for now.
This issue was fixed in https://github.com/apollographql/apollo-client/pull/6588, and will be coming in @apollo/[email protected]
very soon.
Heads up: we're not quite ready to publish @apollo/[email protected]
yet, but I've published an @apollo/[email protected]
release that includes #6588, if you want to try that in the meantime. 馃檹
Update: thanks to @3nvi's reproduction, I can confirm the fix: https://codesandbox.io/s/apollo-reference-equality-t0ttx
Heads up: we're not quite ready to publish
@apollo/[email protected]
yet, but I've published an@apollo/[email protected]
release that includes #6588, if you want to try that in the meantime. 馃檹Update: thanks to @3nvi's reproduction, I can confirm the fix: https://codesandbox.io/s/apollo-reference-equality-t0ttx
Perfect. Thanks a lot @benjamn !
@benjamn I just tried the latest version @apollo/[email protected]
and @apollo/[email protected]
. When I try execute the search below the query gets stuck in an infinite loop, making multiple network requests and never calling onComplete. If I comment out either the fetchPolicy or onCompleted it stops but the combination of both fetch fetchPolicy: 'no-cache'
and onCompleted: data => {}
cause the query to go into an infinite loop.
I tried replicating it in your sandbox but I can only replicate it with @apollo/[email protected]
.
const [search] = useLazyQuery(SearchQuery, {
fetchPolicy: 'no-cache',
onCompleted: data => {},
})
const handleClick = () => {
search({
variables: {
query: 'search',
}
})
}
@apollo/[email protected]
Thank you so much @benjamn ! It's working for me, great!
@benjamn I just tried @apollo/[email protected] and it's still a no go. This issue might be slightly different than the 2 year old one (auto archived without fix and reopened by me https://github.com/apollographql/react-apollo/issues/3968 then repo archived). But the gist for me is that onCompleted
doesn't fire again rendering the concept of a cache useless. I am a bit confused how anyone is able to use a cache as we never have been able to. @Tautorn you say its working for you - is the cache enabled? how are you adding business logic when refetching from cache?
@apollo/[email protected] worked for me. I had to delete the package-lock.json
and run npm install
again. Very odd
@benjamn I just tried the latest version
@apollo/[email protected]
and@apollo/[email protected]
. When I try execute the search below the query gets stuck in an infinite loop, making multiple network requests and never calling onComplete. If I comment out either the fetchPolicy or onCompleted it stops but the combination of both fetchfetchPolicy: 'no-cache'
andonCompleted: data => {}
cause the query to go into an infinite loop.I tried replicating it in your sandbox but I can only replicate it with
@apollo/[email protected]
.const [search] = useLazyQuery(SearchQuery, { fetchPolicy: 'no-cache', onCompleted: data => {}, }) const handleClick = () => { search({ variables: { query: 'search', } }) }
Hi there.
The cache is enabled, but I don't use fetchPolicy
to get any result.
I'm using onCompleted
after query. Before I updated to @apollo/[email protected]
was necessary wrapper useQuery
and useLazyQuery
with useCallback
.
But now working normally (after update version).
const { loading, error } = useQuery(GET_USER, {
onCompleted({ user }) {
setAuth({ user, isAuthenticated: true })
},
})
Still getting stuck with infinite loop of requests to api. it's happening only when i call setAuth to save state with context api.
@zeeshanaligold try adding a flag to check for the user in the onCompleted
method. You may be accidentally retriggering the infinite loop yourself by setting state on each render (which cause it to re-render again)
onCompleted({ user }) {
if (!authedUser) {
setAuth({ user, isAuthenticated: true })
}
}
const { loading, error } = useQuery(GET_USER, { onCompleted({ user }) { setAuth({ user, isAuthenticated: true }) }, })
Still getting stuck with infinite loop of requests to api. it's happening only when i call setAuth to save state with context api.
@zeeshanaligold try with useCallback
:
import { useCallback } from 'react'
...
const handleOnCompleted = useCallback(({ user }) => {
setAuth({ user, isAuthenticated: true })
}, [])
const { loading, error } = useQuery(GET_USER, {
onCompleted: handleOnCompleted
})
@zeeshanaligold try adding a flag to check for the user in the
onCompleted
method. You may be accidentally retriggering the infinite loop yourself by setting state on each render (which cause it to re-render again)onCompleted({ user }) { if (!authedUser) { setAuth({ user, isAuthenticated: true }) } }
@orrybaram it's fix the infinite loop issue but still sending 2 requests.
One more issue when logout trigger & authedUser will set to false then again new request will go to graphql
import { useCallback } from 'react' ... const handleOnCompleted = useCallback(({ user }) => { setAuth({ user, isAuthenticated: true }) }, []) const { loading, error } = useQuery(GET_USER, { onCompleted: handleOnCompleted })
@Tautorn i did try it but still same issue.
@zeeshanaligold Could you reproduction this issue in some tool like codesandbox? If possible I hope.
Does 3.1.1 include this fix?
Not fixed on 3.1.1. It still only works by wrapping onCompleted
and onError
in useCallback
.
3.1.1 is working for me
Not fixed on 3.1.1. It still only works by wrapping
onCompleted
andonError
inuseCallback
.
螜 haven't tested it yet, but make sure the you reset this particular node_module
and that package-lock.json
correctly references the installed version
@benjamn and @hwillson Thanks for tackling this.
As of the moment of writing, in 3.1.1
:
cache-first
query with onCompleted
and/or onError
no longer re-renders endlessly 馃帀 network-only
query no longer pings the server endlessly 馃帀 onCompleted
callback should always fire whenever a query is resolved (either from network or cache). https://github.com/apollographql/react-apollo/issues/4000Currently only
network-only
requests have theironCompleted
fired after every query execution. Forcache-first
ones, theonCompleted
fires only once. Unless this is documented somewhere, I would flag this as a bug since ouronCompleted
logic shouldn't have to account for the origin of this data, i.e. It should be cache agnostic
This should definitely happen for
useQuery
hooks, but not foruseLazyQuery
hooks (since they wouldn't be lazy anymore). Again if this is the expected functionality it should ideally be documented
You can observe both these bugs in the following codesandbox --> https://codesandbox.io/s/apollo-reference-equality-op12z?file=/src/App.js
To reproduce the onCompleted
error: Change fetchPolicy
to cache-first
, click "Run Query" multiple times and see how onCompleted
only fires once
To reproduce the variables
with useLazyQuery
error: Click "Run Query" once and then start clicking "Change foo". Clicking "change foo" should not cause a query to run
Out of the two bugs listed above (in the order that they are mentioned):
Thanks again!
I don't think I've seen this mentioned yet as it probably comes from the same underlying behavior...but a useLazyQuery
with a onCompleted
does not get called again if a user specifies a refetchQueries
array in a useMutation
method call.
Such as
const [updateAlbumReadStatus] = useMutation({
// this triggers a network refetch but does not call the onComplete() of my UnreadAlbums useLazyQuery
refetchQueries: ["UnreadAlbums"]
});
@benjamn
Problem still exists in 3.1.1
for fetchPolicy: "cache-and-network"
(with fetchPolicy: "network-only" all works fine)
I'm on 3.1.1 and onCompleted never calls if a useLazyQuery has its result cached.
Those of you having problems with cache-and-network
or network-only
making unwanted network requests (especially if cache-first
seems to fix things), please read this comment about the new options.nextFetchPolicy
option introduced in v3.1.0: https://github.com/apollographql/apollo-client/issues/6760#issuecomment-668188727
I believe I'm still seeing this on 3.1.3
const client = new ApolloClient({
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
nextFetchPolicy: 'cache-only' // This didn't fix anything
}
},
headers: {
Authorization: authAccessToken(),
Accept: 'application/json, text/plain'
},
uri: url
});
I use that client like so
const { data, error, loading } = useQuery(gql(queries.listDevices), {
client,
variables: {
tenantId: tenantId()
}
});
I can see that it's constantly making network requests, all of which are returning data. Just seems like it can't get out of the loading state. When I log out data
, error
, and loading
, what I see repeated is:
data undefined
error undefined
loading true
I had the same issue with "@apollo/client": "^3.1.3" (loading always false
and no callback).
I found out that the variables
must use react state.
Following code keeps loading
always true
and never goes to onCompleted
callback:
const { loading, error, data } = useQuery(LOGS, {
variables: {
dateFrom: new Date().toISOString()
}
}
Following code works but still have to add notifyOnNetworkStatusChange: true
for the onCompleted
callback:
const [dateFrom, setDateFrom] = useState(new Date().toISOString());
const { loading, error, data } = useQuery(LOGS, {
notifyOnNetworkStatusChange: true,
variables: {
dateFrom
}
}
I solved my specific issue. I was creating the client within the component, so it was recreating the client each time the results were returned. I changed it to:
const client = useRef(getClient());
const { data, error, loading } = useQuery(gql(queries.listDevices), {
client: client.current,
variables: {
tenantId: tenantId()
}
});
The getClient
function does the new ApolloClient
stuff from above.
@gtb104 There's a context hook for that! See useApolloClient
, which works if you're using ApolloProvider
somewhere further up your component tree.
I just discovered that after upgrading from apollo 2 to 3.1.5 that my useLazyQuery
are not always firing the onCompleted. Current workaround for me seems to be changing usage of useLazyQuery to:
client.query({ query: <QUERY> }).then(res =>{ ... } );
Those of you having problems with
cache-and-network
ornetwork-only
making unwanted network requests (especially ifcache-first
seems to fix things), please read this comment about the newoptions.nextFetchPolicy
option introduced in v3.1.0: #6760 (comment)
For this bug: Changing a query variable on a lazy query should not automatically fire it.
, I don't think options.nextFetchPolicy
can be of any help, and I believe there's no workaround.
@3nvi I believe both of the problems demonstrated in your reproduction are related to the fact that useLazyQuery
uses client.watchQuery
behind the scenes, when it should just be using client.query
:
network-only
query when a variable changes is expected if the query is being watched, but would not happen if the query only executes when explicitly requested.onCompleted
not firing is more loosely related, but I'm sure that's something we could fix in the process of rewriting useLazyQuery
to avoid watching, since we should be able to hook into the Promise
returned by client.query
.Although we're about to release v3.2.0 with the changes that are currently on the release-3.2
branch, we haven't forgotten about this issue. We hope to include a fix in a 3.2.x (or a 3.3 beta) version soon.
@benjamn I have a problem with useLazyQuery that I feel is similar and might be related to the cache. I execute a useLazyQuery
based on an id and fragment that I know are already in the cache. The query never shoots off a network query (presumably bc the element is in the cache), loading never becomes true, neither the error key, but neither does it update with data
key. The data key remains undefined. onCompleted
is also never called. Setting the fetchPolicy
to network-only
or no-cache
or setting notifyOnNetworkStatusChange: true
has no effect. (tried from 3.1.5 to 3.2.4 + 3.3.0-beta.12)
I also have an issue with useLazyQuery
that I think might relate to this?
I have an ApolloClient
instance that I use in my ApolloProvider
that I need to reinitialize once in a while because a couple of settings change during the lifetime of the app (mostly HTTP headers and connection params). Therefore, instead of having the client be created outside the scope of my React tree, it's generated within a custom hook. However, it seems like every time there's a new value passed to ApolloProvider
for the client
prop, all queries using useLazyQuery
are relaunched automatically -- and they seem to be launched with the latest variables they were called with. I would expect that they wouldn't be launched until there's another call to the functions provided by the useLazyQuery
hooks, right?
Maybe there's something wrong with the way I'm reloading the client though, and that it shouldn't go through React updates. I couldn't find any documentation on it. Let me know!
The bug calling onCompleted looks like a long-running holdover from 2.x.
https://github.com/apollographql/react-apollo/issues/2177
Although we're about to release v3.2.0 with the changes that are currently on the release-3.2 branch, we haven't forgotten about this issue. We hope to include a fix in a 3.2.x (or a 3.3 beta) version soon.
@benjamn Given the fact that 3.3.0 launched without it, is there a chance we can get an update on the state of things? Is it something that's within your priorities or should we try and work around this for the time being.
As always, I appreciate you' re juggling a lot of things, so thanks in advance
As always, I appreciate you' re juggling a lot of things, so thanks in advance
:+1: on this, thanks for the work you're putting into this project!
Any update on this? I'm getting this error on version
"@apollo/client": "^3.2.5",
"apollo": "^2.31.1",
I found an _orthodox_ solution, implemented useEffect which acts as a _completed_ validator:
const { loading, data, error } = useQuery( MY_QUERY, {
variables: { id },
},
);
Then my useEffect:
useEffect(() => {
if (!data) return;
// your onCompleted logic here
}, [data]);
Most helpful comment
@benjamn and @hwillson Thanks for tackling this.
Current State of things
As of the moment of writing, in
3.1.1
:cache-first
query withonCompleted
and/oronError
no longer re-renders endlessly 馃帀network-only
query no longer pings the server endlessly 馃帀Pending bugs
onCompleted
callback should always fire whenever a query is resolved (either from network or cache). https://github.com/apollographql/react-apollo/issues/4000Reproduction of pending bugs
You can observe both these bugs in the following codesandbox --> https://codesandbox.io/s/apollo-reference-equality-op12z?file=/src/App.js
To reproduce the
onCompleted
error: ChangefetchPolicy
tocache-first
, click "Run Query" multiple times and see howonCompleted
only fires onceTo reproduce the
variables
withuseLazyQuery
error: Click "Run Query" once and then start clicking "Change foo". Clicking "change foo" should not cause a query to runAdditional Context
Out of the two bugs listed above (in the order that they are mentioned):
Thanks again!