apollo
apollo copied to clipboard
"onResult" called only once when same query used in two components
Describe the bug using onResult hook, when the exact same query is also used within another component, will only be executed within one component. I'm not 100% sure if this is a bug but if not, it should be probably mentioned in the docs.
To Reproduce
- create two components using the same query
- add onResult() hook two both of them
- render both components at the same time
- onResult() will be triggered only once, not in both components
Expected behavior
- onResult shall be triggered in all components where used
Versions vue: 2.6.12 @vue/apollo-composable: 4.0.0-alpha.12 @vue/apollo-util: 4.0.0-alpha.6 @vue/composition-api": 1.0.0-beta.25,
That's misleading indeed. Also onResult()
is executed only on first component mount with default fetch policy. If component was previously mounted and query response was fully cached by apollo, onResult()
will not be executed on next component mount.
Just experienced this as well. The fetch policy was set to cache-first
. In first component (on empty cache), onResult
was triggered. In second component (populated cache), onResult
was never triggered. Workaround is that in the second case, the result
from useQuery
already contains the data you need, so we did something like this:
const { result, onResult } = useQuery(...);
const handleResult = (data) => {
// Do side effect based on result
};
// Handle if result is already loaded from cache
if (result.value) handleResult(result.value);
// Handle if result is to be fetched from server
onResult(({ data }) => handleResult(data));
Generally, in team settings this is still problematic as it requires the team members to know the nuance of when to use the data from result
, and when to use the onResult
. If you're not familiar with this behaviour, the debugging isn't easy neither, bc if you test the components in isolation, and the query is executed only once, everything works.
A suggested solution to make it more intuitive would be to trigger onResult
immediately if the result comes from the cache. (e.g. similar behaviour to watch with immediate: true
).
@JuroOravec, if you use a watch
on result
it pretty much solves the issue you're having with needing team members to know the nuance between onResult
and result
.
const { result, onResult } = useQuery(...);
const handleResult = (data) => {
// Do side effect based on result
};
watch(result, () => {
// Needed for the first time the component is mounted as the watch is invoked
// immediately and result will not have any data.
if (!result.value) {
return;
}
handleResult(result.value);
}, {
// Needed for when the component is mounted after the first mount, in this case
// result will already contain the data and the watch will otherwise not be triggered.
immediate: true
});
onResult(({ data }) => {
// Perform actions only needed when data is returned from server.
});
I think in most of cases you will not need the onResult
and just using result
will be good enough. I was having the same problem as described in this issue and the setup above is what I am using now in my project. It works as intended and it doesn't have the issue of needing both onResult
and result
.
Using a watch
with immediate: true
is what worked for me. immediate
guarantees the handler will run on setup as well as when result
changes.
const { result } = useQuery(...)
let channel = reactive({ name: '' })
watch(
() => result,
(res) => channel = { ...res.value?.channel },
{ immediate: true },
)
I have the same issue, on the server watch
with immediate: true
isn't call twice. The main problem is in the hydration of the data, because need to perform the side effect. I'm using Nuxt.JS 2 and I'm trying to upgrade to Apollo 3. In my case, i was to able achieve the desired behavior like this:
const { result, onResult } = useQuery(...);
const handleResult = (data) => {
if (!result.value) {
return;
}
// Do side effect based on result
};
if (process.server) {
onResult(handleResult);
} else {
watch(result, handleResult, { immediate: true });
}
Can't just onResult be called with cached result also? It'd solve all problems.
Can't just onResult be called with cached result also? It'd solve all problems.
100% agree 👍
{fetchPolicy: "network-only"}
(or "no-cache"
) (documentation) seems to work as a band-aid solution.
{fetchPolicy: "network-only"}
(or"no-cache"
) (documentation) seems to work as a band-aid solution.
No doubt, but caching the response offers great UX benefits.
Calling onResult
systematically (including with cached results) would solve the problem.
I find a solution to force trigger onResult method
you can create a ApolloLink to handle response and in the link, set a random property to response ,make the every query's response is different
like this forceOnResultFlag
const timerLink = new ApolloLink((operation, forward) => {
// Called before operation is sent to server
operation.setContext({ start: new Date() })
console.log(`[GraphQL >>>][${operation.operationName}]`, operation.variables)
return forward(operation).map((res) => {
// Called after server responds
res.data!.forceOnResultFlag = new Date().getTime()
const time = new Date().getTime() - operation.getContext().start.getTime()
console.log(`[GraphQL <<<][${operation.operationName}][${time}ms]`, res.data)
return res
})
})
remember add the link to your apolloClient
const apolloClient = new ApolloClient({
link: from([timerLink, errorLink, authLink, httpLink]),
cache: new InMemoryCache(),
defaultOptions: defaultOptions,
})
One thing that worked for me, was to get the variables of the query back and reset them. I could even use cache.
This is the query declaration (note the useLazyQuery)
const {
onResult,
loading,
variables: queryVariables,
load: loadQuery,
} = useLazyQuery(myQuery, null);
Then at some point I have a button that set variables and triggers the load:
queryVariables.value = {
a: 1,
b: 2,
};
loadQuery();
and on the onResult
I clear the variables after the desired effect:
onResult(result => {
// ... stuff happening
queryVariables.value = {};
});
Thanks, @tbusser-io for the immediate: true
solution, it solved the issue for me. Using result
directly might fix this problem but I was relying on onResult
for some side effects on the result
. I found creating a wrapper around useQuery
that calls onResult
with the cached results would be the best workaround. Something like:
/**
* Workaround for `result` and `onResult` not triggering for cached data.
* This behaviour causes the side effects in onResult or watchers to fail and thereby shows empty data
* @param {Document} query GraphQL Tag
* @param {Object} variables Query variables. Can be Object or function
* @param {Object} options Options for useQuery
* @returns {Object}
*/
function useReactiveQuery (query, variables, options) {
const q = paramToRef(query);
const v = paramToRef(variables);
const o = paramToRef(options);
const result = ref();
const { refetch, result: queryResult, onResult: onQueryResult, loading, error } = useQuery(q, v, o);
watch(queryResult, res => {
result.value = res;
}, { immediate: true });
onQueryResult(response => {
result.value = response.data;
})
return { result, onResult: onQueryResult, loading, error, refetch };
};
I've made helper package that helps with this and some other problem I've faced in all my projects, feel free to use it till this issue wont be resolved.
Why does onResult
even exist if it's not called even after a refetch
and we should just watch
the result anyway?
Bump, this drove me insane because I couldn't figure out why onResult doesn't get called after remounting my component. The documentation is misleading: "onResult: This is called whenever a new result is available."
Can't just onResult be called with cached result also? It'd solve all problems.
This is the way to go and should be addressed immediately. Or at least for the time being the documentation should be updated with a disclaimer that onResult is not called when the result is coming from the cache.
Could not manage the same problem. I tried to depend on refetchQueries: 'active'
to refresh state of component. But onResult didn't work if response from server was the same as last time. Watching result didn't help also