The skipToken
option worked for me.
Glad to hear it! To be honest, I think you’re the first one to use this new feature, so congrats

do you see suspense as useful for queries that are to be run in response to user events (e.g. a button being clicked)
Absolutely! In fact, Relay’s useQueryLoader/usePreloadedQuery hooks work this way. The hook that we will release in 3.9 will resemble this a little bit.
The case I think it will be super nice for is preloading a part of your UI. For example, imagine hovering over a button that opens a modal which displays some data. If you can start loading that data on hover vs waiting for the modal to render, the modal is going to appear much snappier because you may be able to load the data faster than the user clicks to open it. If the user does happen to open the modal before the query is finished, Suspense will take over and you’ll see the loading state instead. This pattern seems like a natural fit for this type of API.
I’m sure others will come up with great ideas and patterns for it. Its certainly a much more streamlined API than having to skip a query and pair it with a useState
for these types of preloading scenarios.
I’d recommend taking a read of this excellent Relay tutorial that describes some of the benefits of this pattern. Again, our hook will look a bit different than this one, but the concepts will be similar.
We’ll have much more to share in the coming weeks as we start working on this new hook and start our 3.9 prerelease series (alphas, betas, etc).
Or is “suspending” only something that makes sense in renders, and not in event handlers?
Think of Suspense more like a “not ready” state for your component, where that “not ready” state is determined during a render cycle. Its not so much that the event handler itself starts suspending the component, its that state updates caused by that event handler will cause the component to Suspend on the next render. Does that difference make sense?
Also, if I’m not mistaking, calling the query function returned by useLazyQuery
also causes a re-render.
This is correct, though the reason for this is due to the value of the 2nd item in the tuple returned by useLazyQuery
.
const [loadQuery, { data, loading, error }] = useLazyQuery()
Those data
/loading
/error
states need to reflect the state of the most recent call to the query function. In this way, useLazyQuery
is managing the query state for you. You can almost think of this hook as useQuery
with skip
set to true
until you call the query function for the hook. Unless you absolutely have a need to useState
for something extra, I’d recommend just using the data
property returned by this hook instead of calling your own set
function. This will reduce a render for you.
One other important point to make here is that useLazyQuery
will respond to cache updates. This is a point a lot of people tend to miss with this hook. If you use your own state setter for to store data from this hook, data changing in response to cache updates are going to be missed, which means your UI might get out of sync/outdated, especially if you run mutations that alter the data returned from this query.
Its not against the rules to store your own state using data returned from the query function, but I’d recommend avoiding it and just using data
if you’re not doing anything special with it.
I have a use case where I think that what I want is a lighter-weight way to call a GraphQL query: when a certain user action happens, I want to call a query to load more data, and I don’t need this call to trigger a re-render
Funny enough, people tend to forget you can just use the client
directly to make queries (myself included). There is nothing that says you have to use the hooks to query data. You’ll just get the additional benefit that those hooks will ensure your component rerenders when cache updates are made to data in the query. The hooks will also manage the loading state for you.
If you don’t care about cache updates, I’d just call client.query({ query })
directly:
import client from './path/to/client';
function MyComponent() {
const [data, setData] = useState(null);
return (
<button
onClick={async () => {
const { data } = await client.query({ query });
setData(data);
}}
/>
);
}
Apollo also returns a useApolloClient
hook if you prefer to get the client
that was passed into ApolloProvider
import { useApolloClient } from '@apollo/client';
function MyComponent() {
const client = useApolloClient();
// ...
}
Again, the key difference here is that you’ll miss cache updates by using this directly instead of the hooks. Certainly a tradeoff you can make a decision on for your app.
Hope this helps! Please let me know if you have any additional questions.