eddev

Query Hooks

Generate browser runtime query hooks from files in queries.

Query hooks are for data that should be fetched at runtime in the browser, such as search results, filters, live previews, and user-controlled lookups.

They are generated from GraphQL query files in queries/. The generated hooks live in hooks/queries.ts and wrap TanStack Query.

Query hooks are browser-side runtime fetches. They are not executed as part of the server-rendered route payload, unlike View, Block, and Global Data queries.

Once a query hook renders in the browser, it follows TanStack Query behavior and will run automatically unless you pass enabled: false.

Naming Rules

GraphQL query hook files have strict naming rules enforced by codegen:

  1. Files live under queries/, excluding queries/fragments/.
  2. The filename must start with Use.
  3. The GraphQL operation name must match the filename.
  4. Each file can contain only one operation.

Subdirectories are fine. They affect the runtime query name, but not the hook name.

query UsePostsSearch($q: String) {
  posts(where: { search: $q }, first: 100) {
    nodes {
      title
      uri
    }
  }
}

This generates usePostsSearch in hooks/queries.ts.

Basic Usage

import { Link } from "eddev/routing"
import { useState } from "react"
import { usePostsSearch } from "@hooks/queries"

export function ExampleSearch() {
  const [search, setSearch] = useState("")
  const lookup = usePostsSearch(
    { q: search },
    { enabled: search.length >= 3 },
  )

  return (
    <div>
      <input type="search" value={search} onChange={(e) => setSearch(e.currentTarget.value)} />

      {lookup.isLoading ? (
        <p>Loading...</p>
      ) : lookup.isError ? (
        <p>{lookup.error.message}</p>
      ) : lookup.data ? (
        <ul>
          {lookup.data.posts?.nodes?.map((node) => (
            <li key={node.uri}>
              <Link href={node.uri!}>{node.title}</Link>
            </li>
          ))}
        </ul>
      ) : null}
    </div>
  )
}

Use enabled: false or an expression like enabled: search.length >= 3 when the query should wait for user input.

Hook API

Generated query hooks accept:

  1. Query variables, or undefined if the query has no variables.
  2. TanStack Query options, except queryKey and queryFn, which eddev manages.
const lookup = usePostsSearch(
  { q: search },
  {
    enabled: search.length >= 3,
    staleTime: 60_000,
    refetchOnWindowFocus: false,
  },
)

The returned object is a TanStack UseQueryResult, including:

type QueryResult<T> = {
  status: "pending" | "error" | "success"
  isLoading: boolean
  isSuccess: boolean
  isError: boolean
  error: QueryError | null
  data: T | undefined
  refetch: () => Promise<unknown>
}

Manual Fetching

Each generated query hook also has a static .fetch() method for rare non-hook usage.

import { usePostsSearch } from "@hooks/queries"

async function loadPosts() {
  const result = await usePostsSearch.fetch({ q: "festival" })
  return result.posts?.nodes ?? []
}

Prefer the hook in React components so loading, error, cache, and refetch states stay consistent.

On this page