apollo-client
apollo-client copied to clipboard
useLazyQuery automatically triggers after being triggered once
Hello,
I'm trying to use useLazyQuery
to trigger a query on click on a button.
Intended outcome:
It only triggers the query when I click on the button (execQuery()
)
Actual outcome:
After clicking once on the button, it triggers the query automatically when the input changes (value
)
How to reproduce the issue:
function Component() {
const [value, setValue] = useState('');
const [execQuery, { loading, data, error }] = useLazyQuery(SEGMENT_QUERY, {
variables: { value } },
});
return (
<>
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
<button onClick={() => execQuery()}>dewit</button>
{loading && 'loading'}
{error && 'error'}
<pre>{data && JSON.stringify(data, null, 2)}</pre>
</>
);
}
Using the example above:
- Type some text in the text input, you can see in the Network tab of developer tools that no query is triggered (this is expected).
- Click on the button, the query is correctly triggered (this is expected)
- Now go back to typing in the text input, and you can see in the Network tab that a query is triggered on each key stroke (this is unexpected: the query should be triggered only when the button is clicked)
Also I tried to use useQuery
with skip: true and refetch, but calling refetch has no effect
Versions
$ npx envinfo@latest --preset apollo --clipboard
System: OS: Windows 10 10.0.19042 Binaries: Node: 14.4.0 - C:\Program Files\nodejs\node.EXE Yarn: 1.22.10 - ~\AppData\Roaming\npm\yarn.CMD npm: 7.24.1 - C:\Program Files\nodejs\npm.CMD Browsers: Chrome: 97.0.4692.71 Edge: Spartan (44.19041.1266.0), Chromium (97.0.1072.62) npmPackages: apollo-server-core: ^3.5.0 => 3.5.0 apollo-server-express: ^3.5.0 => 3.5.0
I wrote this hook as a temporary workaround
// useMyLazyQuery.ts
import { ApolloQueryResult, OperationVariables, QueryOptions, TypedDocumentNode, useApolloClient } from '@apollo/client';
import { useCallback, useRef, useState } from 'react';
export type MyLazyQueryResult<T> = Omit<ApolloQueryResult<T>, 'networkStatus' | 'data'> & { data: T | undefined };
export default function useMyLazyQuery<T = any, TVariables = OperationVariables>(
query: TypedDocumentNode<T, TVariables>,
options: Omit<QueryOptions<TVariables, T>, 'query'>
): [() => Promise<void>, MyLazyQueryResult<T>] {
const client = useApolloClient();
const self = useRef<undefined | {}>(undefined);
const [result, setResult] = useState<MyLazyQueryResult<T>>({
loading: false,
data: undefined,
});
const execQuery = useCallback(async () => {
const current = {};
self.current = current;
try {
setResult({
loading: true,
data: undefined,
});
const queryResult = await client.query({
query,
...options,
});
if (self.current !== current) {
// query canceled
return;
}
setResult({
loading: false,
data: queryResult.data,
error: queryResult.error,
});
} catch (error: any) {
if (self.current !== current) {
// query canceled
return;
}
setResult({
loading: false,
data: undefined,
error,
});
}
}, [client, query, options]);
return [execQuery, result];
}
@madhugod I will look into that.
You are right, the issue is there, and the unit test that I just wrote covers it. I will look at your solution and let's see what we can do to solve it.
When I thought a little longer about the issue I have some thoughts.
The current behavior is the same as useQuery
and when variables will change then the query will re-fetch.
To make useLazyQuery
bulletproof we should make it simpler.
@benjamn what do you think if we will remove options from useLazyQuery
and we will be able to pass it only during execution?
const [execQuery, { loading, data, error }] = useLazyQuery(SEGMENT_QUERY)
...
<button onClick={() => execQuery({ variables: { value } })}>Submit</button>
So in this way, we will do not need to fix it ( if we consider it as a bug ) and developers will do not think that much about how to use useLazyQuery
.
@sztadii Personally I like this idea, it would make it work like a useMutation
which I think is easier :)
@madhugod so there are two of us that see it useful. We will need owners to agree on that 🤞
I'd be quite glad with that change
Let's make it three, it's crazy that this is not the default. Makes me wonder how else are people using this.
I am also experiencing the same issue, the update of a state that changes the query variable automatically triggered the query to execute.
Hi, experiencing same issue with latest Apollo version 3.6.2. @sztadii good point, but isn't it workaround what you described? I mean, in general shouldn't useLazyQuery should work the way that it shouldn't be triggered on variables change?
I also noticed one issue with useQuery(Maybe this was intentional for new Apollo version), but still would like to point out and hear some thoughts. So here is example:
- Have a paginated list.
- Have a typePolicy custom merge function where I spread and return [...existingList, ...incomingList] (Unique by reference)
- Using const { data, loading, fetchMore } = useQuery(someQuery, { variables: { hasSeen: activeValue } }). 4 Initial activeValue is true and I do fetchMore and add 10 more items in a list. (Now we have 20 items in cache)
- If now I change activeValue to false (Which is in local state), useQuery will be triggered, which brings 10 new items, but in my custom merge function existingList contains old data as well, so I assume that useQuery doesn't do refetch on variable change.
So I wandering was this intentional change for new Apollo(3.6.2), as far as I remember on version 2.3 it was doing refetch and whole pagination was starting from initial state. Any thoughts? Thank you.
@sztadii with useQuery, it seems logical to me that it refetch, but not for useLazyQuery as it should only redefine a new function (execQuery in the first example). Options for useLazyQuery aren't supposed to be "default options" while defining live options should be set with the return function
in author's case, in my opinion, it should be used like : `const [execQuery, { loading, data, error }] = useLazyQuery(SEGMENT_QUERY);
execQuery({ variables: { value } }, })`
Hello, I'm facing some issues with useLazyQuery. Please take a look at the code snippet below:
import { useLazyQuery } from "@apollo/client";
import { useErrorContext } from "../contexts/handle-error.context";
import { UserByTokenQuery } from "../apis/queries/user.query";
import { useCallback } from "react";
export const useQueryHook = () => {
const { handleError, clearError } = useErrorContext();
const [getUserByToken] = useLazyQuery(UserByTokenQuery, {
onError: (err) => {
console.log(22, err);
},
});
const handleGetUserByToken = useCallback(async () => {
console.log(1111);
const accessToken = window.sessionStorage.getItem("authenticated");
if (!accessToken) return;
const result = await getUserByToken({ variables: null });
clearError();
return result;
}, [clearError, getUserByToken]);
return {
handleGetUserByToken,
};
};
import { useMutation } from "@apollo/client";
import { SignInMutation, SignInVariables } from "../apis/mutations/signOut.mutation";
import { SignOutMutation } from "../apis/mutations/signIn.mutation";
import { useErrorContext } from "../contexts/handle-error.context";
import { useCallback } from "react";
export const useMutationHook = () => {
const { handleError, clearError } = useErrorContext();
const [signInMutation] = useMutation(SignInMutation, {
onError: handleError,
});
const [signOutMutation] = useMutation(SignOutMutation, {
onError: (err) => console.log(211222, err),
});
const handleSignIn = useCallback(
async (username, password) => {
const result = await signInMutation({
variables: SignInVariables(username, password),
});
clearError();
return result;
},
[clearError, signInMutation]
);
const handleSignOut = useCallback(async () => {
console.log("signout ne");
await signOutMutation();
clearError();
}, [clearError, signOutMutation]);
return {
handleSignIn,
handleSignOut,
};
};
import { useRouter } from "next/router";
import { useMutationHook } from "./use-mutation";
import { useQueryHook } from "./use-query";
import { useLazyQuery } from "@apollo/client";
import { UserByTokenQuery } from "../apis/queries/user.query";
import { useErrorContext } from "../contexts/handle-error.context";
import { useCallback } from "react";
export const useActionHook = () => {
const router = useRouter();
const { handleError } = useErrorContext();
const { handleSignIn, handleSignOut } = useMutationHook();
const { handleGetUserByToken } = useQueryHook();
const loginAction = useCallback(
async (username, password) => {
const { data } = await handleSignIn(username, password);
const { accessToken } = data.signIn;
window.sessionStorage.setItem("authenticated", accessToken);
console.log("loginAction ne");
const { userByToken } = (await handleGetUserByToken())?.data;
return userByToken;
},
[handleGetUserByToken, handleSignIn]
);
const logoutAction = useCallback(async () => {
await handleSignOut();
router.push("/auth/login");
}, [handleSignOut, router]);
return {
loginAction,
logoutAction,
};
};
When logoutAction is called, I navigate the user to the login page. However, I don't understand why handleGetUserByToken is being called, and since I have deleted the token, it causes an error.
I don't understand why handleGetUserByToken is being triggered automatically each time my page is re-rendered.
versions: node: v18.15.0 "@apollo/client": "^3.7.17", "react": "18.2.0",
@ankitruong , @jbcrestot , @sztadii
I was also facing a similar use and the issue in my case was that I had the dynamic query implemented with useLazyQuery()
.
As per the useLazyQuery()
implementation, it subscribes to the subsequent changes to the cache. This means that the data always get recomputed and it has different values.
Maybe where the query should be fired when required in such situations. client.query()
seems a good choice.
Here is the sample code
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
// Initialize Apollo Client
const client = new ApolloClient({
uri: 'https://your-graphql-endpoint.com',
cache: new InMemoryCache()
});
// Define your query
const GET_DATA = gql`
query GetData {
data {
id
name
}
}
`;
// Use client.query() to fetch data
client.query({ query: GET_DATA })
.then(response => console.log(response.data))
.catch(error => console.error(error));
Any updates on this?
I am still not sure whether you consider the automatic trigger a bug or expected behaviour, see https://github.com/apollographql/apollo-client/issues/7484#issuecomment-925314635.
However, I could implemented the expected behaviour using useQuery
in combination with skip
: skip
is only true when data should be fetched, i.e. it is false when the user changes the value in the input field and set to true when the user clicks on the search button. This requires another useState
hook for skip
.
@madhugod How did you solve it in the end?
@tobiasschweizer I created a custom hook, see https://github.com/apollographql/apollo-client/issues/9317#issuecomment-1014792893
So it's a permanent workaround now ;-)