Skip to Content
Docs
GraphQL
Query Hooks

Query Hooks

Sometimes you may need to perform queries at runtime, such as searching/filtering data based on user input. In these instances, you can use dynamic queries, which are executed at runtime, and are triggered from the users browser.

To do so, we create .graphql files inside the queries/ folder, and use the auto-generated React hooks to call those queries inside our components.

React Hooks are automatically generated by the dev CLI process, based on the name of the query. The generated hooks are a light wrapper over Tanstack Query , and the usage options + return types are maintained. The only difference is that the fetching process and parameter serialization are handled for you.

Generated hooks are stored in hooks/queries.ts, and can be imported from there using the name of the query file.

Query hooks are excuted lazily, and only in the browser. This makes sense when performing a database query in response to a user selecting a filter option, but it can appear as slow if it happens when first opening a page, and content loaded in this way is not easily indexable by search engines.

As such, you should use query hooks with caution.

If the data you need to access is static, then consider moving that query into a View or Block query, or even the Global Data query instead.

Naming Conventions

  1. All queries/*.graphql files (except for queries/fragments/*.graphql) should start with Use (eg. queries/UseLatestPosts.graphql).
  2. The name of your query file and the name of the query operation should also match.
  3. You can use subdirectories to organise queries and mutations, within the queries/ folder. The name of the generated hook will not be affected.

This naming convention allows us to keep our queries well-organised, and keeps our TypeScript/GraphQL code readable/searchable.

Basic Usage

After writing a query file in the /queries directory (optionally nested within a subdirectory like below), the dev CLI tool should auto-generate a new function, called usePostsSearch within hooks/queries.ts.

queries/posts/UseLatestPosts.graphql
query UsePostsSearch($q: String) { posts(where: {search: $q}, first: 100) { nodes { title uri } } }

Once generated, you can call the hook, passing input parameters as the first argument. The hook will return an object which contains status information about the query, such as isLoading, isError, error and data.

import { usePostsSearch } from "@hooks/queries" function ExampleSearch() { const [search, setSearch] = useState("") const lookup = usePostsSearch({ q: search, }) return ( <div> {/* A search field */} <input type="search" value={search} onChange={(e) => setSearch(e.currentTarget.value)} /> {lookup.isLoading ? ( <p> {/* Results are loading */} Loading... </p> ) : lookup.isError ? ( <p> {/* An error ocurred while fetching the results */} {lookup.error.message} </p> ) : lookup.data ? ( <ul> {/* We have some results! */} {lookup.data.articles?.nodes.map((node) => { return ( <li key={node.uri}> <Link href={node.uri}>{node.title}</Link> </li> ) })} </ul> ) : null} </div> ) }

Options

Query hooks accept two parameters:

  1. An object containing the input variables for the query, or undefined.
  2. An optional configuration object, to customise the default behaviour of the query.

The options object is optional, but can be used to fine-tune the behaviour of the query:

type UseQueryOptions<T> = { /** * Set this to `false` to disable automatic execution. * Call the `refetch` method to manually fetch the query, or just provide an expression which swaps to `true` when the query should execute */ enabled?: boolean /** * The time in milliseconds after data is considered stale. * If set to `Infinity`, the data will never be considered stale. * If set to a function, the function will be executed with the query to compute a `staleTime`. */ staleTime?: number /** * If set to a number, the query will continuously refetch at this frequency in milliseconds. * Defaults to `false`. */ refetchInterval?: number | false /** * If set to `true`, the query will continue to refetch while their tab/window is in the background. * Defaults to `false`. */ refetchIntervalInBackground?: boolean; /** * If set to `true`, the query will refetch on window focus if the data is stale. * If set to `false`, the query will not refetch on window focus. * If set to `'always'`, the query will always refetch on window focus. * Defaults to `true`. */ refetchOnWindowFocus?: boolean | 'always' /** * If set to `true`, the query will refetch on reconnect if the data is stale. * If set to `false`, the query will not refetch on reconnect. * If set to `'always'`, the query will always refetch on reconnect. * Defaults to the value of `networkOnline` (`true`) */ refetchOnReconnect?: boolean | 'always' /** * If set to `true`, the query will refetch on mount if the data is stale. * If set to `false`, will disable additional instances of a query to trigger background refetch. * If set to `'always'`, the query will always refetch on mount. * Defaults to `true`. */ refetchOnMount?: boolean | 'always' /** * If set to `false`, the query will not be retried on mount if it contains an error. * Defaults to `true`. */ retryOnMount?: boolean; /** * If set to `true`, the query will suspend when `status === 'pending'` * and throw errors when `status === 'error'`. * Defaults to `false`. */ suspense?: boolean; }

Hook API

The hook will always return an object with the following properties:

type UseQuery<T> = { /* The current status of the query */ status: "pending" | "error" | "success" /* True if status === 'pending' */ isLoading: boolean /* True if status === 'success' */ isSuccess: boolean /* True if status === 'error' */ isError: boolean /* The error object if an error occurred during the query. */ error: QueryError | null /* The data returned from the server, or `undefined` if the query has not completed yet. */ data: T | undefined /** A function to manually refetch the query. */ refetch: () => void }

Additional options, properties and documentation can be found in the TanStack Query Documentation , noting that the queryFn and queryKey are handled automatically and are not available.

Calling Manually

In rare instances, you may want to call a query outside of React — like within a Serverless Function, or in a more complex set of functions.

To do this, you can access the fetch method on the hook itself.

import { usePostsSearch } from "@hooks/queries" async function doSomeStuff() { const result = await usePostsSearch.fetch({ s: 'Zoe and Piper' }) console.log('The results are', result) }

Most of the time, you should stick to calling queries using React hooks, since the hook version will handle loading and error states, as well as automatically re-fetching stale data.

Last updated on