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:
- Files live under
queries/, excludingqueries/fragments/. - The filename must start with
Use. - The GraphQL operation name must match the filename.
- 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:
- Query variables, or
undefinedif the query has no variables. - TanStack Query options, except
queryKeyandqueryFn, 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.