createQuery
createQuery creates a reusable query object with pre-configured options. Perfect for route loaders, centralized query definitions, and cache manipulation outside React components.
Import
Section titled “Import”import { createQuery, createQueryFactory } from "@ic-reactor/react"Basic Usage
Section titled “Basic Usage”import { createQuery } from "@ic-reactor/react"import { backend } from "./reactor"
const userProfileQuery = createQuery(backend, { functionName: "getProfile", args: [currentUserId], staleTime: 5 * 60 * 1000, // 5 minutes})Configuration
Section titled “Configuration”| Option | Type | Default | Description |
|---|---|---|---|
functionName |
string |
Required | The canister method to call |
args |
Args |
[] |
Arguments for the method |
select |
(data) => Selected |
- | Transform data before returning |
staleTime |
number |
300000 |
Time before data is stale (ms) |
queryKey |
QueryKey |
Auto | Custom query key segments |
Return Value
Section titled “Return Value”The factory returns an object with these utilities:
| Property | Type | Description |
|---|---|---|
fetch |
() => Promise<T> |
Cache-first fetch — use in route loaders |
prefetch |
() => Promise<void> |
Fire-and-forget cache warm-up |
useQuery |
(options?) => UseQueryResult |
React hook for components |
invalidate |
() => Promise<void> |
Invalidate cache (refetches if active) |
getQueryKey |
() => QueryKey |
Get the TanStack Query key |
getCacheData |
(select?) => T |
Read from cache without fetching |
setData |
(updater) => T | undefined |
Write raw data into cache |
Examples
Section titled “Examples”TanStack Router Integration
Section titled “TanStack Router Integration”Use factories with TanStack Router’s file-based routing and loaders:
// routes/users/$userId.tsximport { createFileRoute } from "@tanstack/react-router"import { createQueryFactory } from "@ic-reactor/react"import { backend } from "../../reactor"import { redirect } from "@tanstack/react-router"
// Create factory (can be in a separate file)const getUserQuery = createQueryFactory(backend, { functionName: "getUser", select: (result) => result.user, staleTime: 5 * 60 * 1000,})
export const Route = createFileRoute("/users/$userId")({ // Prefetch in loader - runs before component mounts loader: async ({ params }) => { const user = await getUserQuery([params.userId]).fetch()
if (!user) { throw redirect("/404") }
return { user } },
// Optional: Define pending component pendingComponent: () => <div>Loading user...</div>,
// Main component component: UserPage,})
function UserPage() { const params = Route.useParams()
// Data is already cached from loader - instant render! const { data: user, isRefetching } = getUserQuery([params.userId]).useQuery()
return ( <div> <h1>{user.name}</h1> {isRefetching && <span>Refreshing...</span>} </div> )}TanStack Router with Route Context
Section titled “TanStack Router with Route Context”Pass the reactor via router context for better organization:
import { createRouter, createRootRouteWithContext,} from "@tanstack/react-router"import { backend } from "./reactor"
interface RouterContext { backend: typeof backend}
const rootRoute = createRootRouteWithContext<RouterContext>()({ component: RootLayout,})
export const router = createRouter({ routeTree, context: { backend }, defaultPreloadStaleTime: 0, // Let TanStack Query handle caching})
// routes/posts.tsximport { createFileRoute } from "@tanstack/react-router"import { createQuery } from "@ic-reactor/react"
export const Route = createFileRoute("/posts")({ loader: async ({ context }) => { const postsQuery = createQuery(context.backend, { functionName: "getPosts", }) return { posts: await postsQuery.fetch() } }, component: PostsPage,})React Router 6/7 Integration
Section titled “React Router 6/7 Integration”import { createBrowserRouter, json } from "react-router-dom"import { createQueryFactory } from "@ic-reactor/react"import { backend } from "./reactor"
const getUserQuery = createQueryFactory(backend, { functionName: "getUser",})
export const router = createBrowserRouter([ { path: "/users/:userId", loader: async ({ params }) => { const user = await getUserQuery([params.userId!]).fetch() return json({ user }) }, Component: UserPage, },])
// UserPage.tsximport { useLoaderData, useParams } from "react-router-dom"
function UserPage() { // Initial data from loader const { user: initialUser } = useLoaderData() as { user: User } const { userId } = useParams()
// Subscribe to updates const { data: user } = getUserQuery([userId!]).useQuery({ initialData: initialUser, })
return <Profile user={user} />}Select Transformation
Section titled “Select Transformation”Transform data at the factory level:
// Only get what you needconst userNameQuery = createQuery(backend, { functionName: "getUser", args: [userId], select: (user) => user.name, // Only extract name})
// In component - data is just the name stringconst { data: userName } = userNameQuery.useQuery()Chained Select in Hook
Section titled “Chained Select in Hook”Chain additional transformations in the hook:
const userQuery = createQuery(backend, { functionName: "getUser", args: [userId], select: (user) => user.profile, // First transformation})
// Further transform in componentconst { data: avatarUrl } = userQuery.useQuery({ select: (profile) => profile.avatarUrl, // Chained transformation})Conditional Fetching
Section titled “Conditional Fetching”Use enabled option for conditional queries:
const sensitiveDataQuery = createQuery(backend, { functionName: "getSensitiveData", args: [],})
function ProtectedComponent() { const { isAuthenticated } = useAuth()
const { data } = sensitiveDataQuery.useQuery({ enabled: isAuthenticated, // Only fetch when authenticated })
return isAuthenticated ? <Data data={data} /> : <LoginPrompt />}Cache Manipulation
Section titled “Cache Manipulation”Read and manipulate cache outside React:
const userQuery = createQuery(backend, { functionName: "getUser", args: [userId],})
// Read from cache (returns undefined if not cached)const cachedUser = userQuery.getCacheData()
// Read with transformationconst cachedName = userQuery.getCacheData((user) => user.name)
// Force invalidation (refetches if query is active)await userQuery.invalidate()
// Write directly into cache (optimistic updates)userQuery.setData(updatedUser)
// Or use an updater functionuserQuery.setData((old) => ({ ...old, name: "Alice" }))Prefetching on Hover
Section titled “Prefetching on Hover”Prefetch data when the user hovers over a link. Use prefetch() (fire-and-forget)
rather than fetch() here — you don’t need to await a result, just warm the cache:
function UserLink({ userId }: { userId: string }) { const handleMouseEnter = () => { // Fire-and-forget cache warm-up getUserQuery([userId]).prefetch() }
return ( <Link to={`/users/${userId}`} onMouseEnter={handleMouseEnter} > View User </Link> )}createQueryFactory
Section titled “createQueryFactory”For dynamic arguments (like route params), use the factory variant:
import { createQueryFactory } from "@ic-reactor/react"
const getUserQuery = createQueryFactory(backend, { functionName: "getUser", staleTime: 5 * 60 * 1000,})
// Create query instances with different argsconst aliceQuery = getUserQuery(["alice"])const bobQuery = getUserQuery(["bob"])
// Use in componentsconst { data: alice } = aliceQuery.useQuery()const { data: bob } = bobQuery.useQuery()Factory Caching
Section titled “Factory Caching”Factory instances are cached internally:
// These return the SAME object instanceconst query1 = getUserQuery(["alice"])const query2 = getUserQuery(["alice"])
console.log(query1 === query2) // trueThe cache holds the 256 most recently used argument sets. That covers any realistic working set, but a factory called with an open-ended range of arguments — one query per principal in a long-lived session, a search-as-you-type filter, a cursor-keyed list — will eventually evict the oldest entries rather than grow without bound.
Eviction costs memoization, not correctness: a query object is a closure over the reactor and its config, so one rebuilt after eviction behaves identically and only its reference identity differs. It matters only if you compare instances or put one in a dependency array and cycle through more than 256 distinct argument sets.
See Also
Section titled “See Also”- createSuspenseQuery — Suspense version
- createInfiniteQuery — Paginated queries
- useActorQuery — Direct hook usage
- Queries Guide — Query patterns