React
λ Query provides first-class React 19 bindings with components and hooks that integrate with Suspense and Transitions. All React APIs are imported from @studiolambda/query/react.
QueryProvider
Section titled “QueryProvider”Every React application using λ Query needs a QueryProvider at the top of the tree. It provides the query instance via context and manages the broadcast channel lifecycle.
import { QueryProvider } from "@studiolambda/query/react";
function App() { return ( <QueryProvider> <MyApp /> </QueryProvider> );}By default, QueryProvider creates a new query instance. To provide your own:
import { createQuery } from "@studiolambda/query";import { QueryProvider } from "@studiolambda/query/react";
function App() { const query = createQuery({ expiration: () => 10_000, });
return ( <QueryProvider query={query}> <MyApp /> </QueryProvider> );}| Prop | Type | Default | Description |
|---|---|---|---|
query | Query | createQuery() | The query instance to provide. |
clearOnForget | boolean | false | When true, forgetting a key triggers an immediate refetch. When false, stale data remains until the next manual query. |
ignoreTransitionContext | boolean | false | When true, hooks always create their own transition instead of using a shared QueryTransition context. |
useQuery
Section titled “useQuery”The primary hook for fetching and managing data. It combines data fetching, status tracking, and mutation capabilities.
import { useQuery } from "@studiolambda/query/react";
interface Post { id: number; title: string; body: string;}
function PostCard({ id }: { id: number }) { const { data, isPending, isExpired, refetch, mutate, forget } = useQuery<Post>(`/api/posts/${id}`);
return ( <article style={{ opacity: isPending ? 0.7 : 1 }}> <h2>{data.title}</h2> <p>{data.body}</p> <button onClick={() => refetch()}>Refresh</button> </article> );}The component suspends until data is available. After the first load, data is always the resolved value — no null checks needed.
Return Value
Section titled “Return Value”| Property | Type | Description |
|---|---|---|
data | T | The resolved data. Always available (component suspends until ready). |
isPending | boolean | true while a React transition is in progress (refetching or mutating). |
expiresAt | Date | When the cached data expires. |
isExpired | boolean | Whether the data is currently expired. Auto-updates via timer. |
isRefetching | boolean | Whether a background refetch is in progress. |
isMutating | boolean | Whether a mutation is in progress. |
refetch(options?) | Function | Force a refetch. Options override instance defaults for this call. |
mutate(value, options?) | Function | Mutate the cached data. Accepts a direct value or an async function. |
forget() | Function | Remove this key from the cache. |
Options
Section titled “Options”useQuery accepts the same options as the core query() method, plus the QueryProvider options:
const { data } = useQuery<User>("/api/user", { expiration: () => 30_000, stale: false, clearOnForget: true,});Mutations from useQuery
Section titled “Mutations from useQuery”Use the mutate function for optimistic updates:
function UserProfile() { const { data, mutate, isMutating } = useQuery<User>("/api/user");
async function updateName(newName: string) { await mutate(async (previous) => { const updated = await fetch("/api/user", { method: "PATCH", body: JSON.stringify({ name: newName }), }).then((r) => r.json());
return updated; }); }
return ( <div> <h1>{data.name}</h1> {isMutating && <span>Saving...</span>} <button onClick={() => updateName("New Name")}>Rename</button> </div> );}Using an async function for mutations is preferred because it correctly triggers the isMutating state and React transitions.
useQueryStatus
Section titled “useQueryStatus”Get the status of a cached key without fetching:
import { useQueryStatus } from "@studiolambda/query/react";
function CacheStatus({ cacheKey }: { cacheKey: string }) { const { isExpired, isRefetching, isMutating, expiresAt } = useQueryStatus(cacheKey);
return ( <div> <p>Expired: {isExpired ? "Yes" : "No"}</p> <p>Refetching: {isRefetching ? "Yes" : "No"}</p> <p>Mutating: {isMutating ? "Yes" : "No"}</p> <p>Expires at: {expiresAt?.toLocaleTimeString()}</p> </div> );}This hook subscribes to events but does not trigger any fetch. Use it for displaying cache health indicators.
useQueryActions
Section titled “useQueryActions”Get mutation and refetch functions without subscribing to data changes:
import { useQueryActions } from "@studiolambda/query/react";
function DeleteButton({ postId }: { postId: number }) { const { mutate, forget } = useQueryActions(`/api/posts/${postId}`);
async function handleDelete() { await fetch(`/api/posts/${postId}`, { method: "DELETE" }); await forget(); }
return <button onClick={handleDelete}>Delete</button>;}This is useful when a component needs to modify cached data without displaying it — for example, a delete button in a list that doesn’t render the item’s data itself.
Return Value
Section titled “Return Value”| Property | Type | Description |
|---|---|---|
refetch(options?) | Function | Force a refetch. |
mutate(value, options?) | Function | Mutate the cached data. |
forget() | Function | Remove this key from the cache. |
useQueryInstance
Section titled “useQueryInstance”Access the raw query instance from context:
import { useQueryInstance } from "@studiolambda/query/react";
function CacheInspector() { const query = useQueryInstance(); const keys = query.keys("items");
return ( <ul> {keys.map((key) => ( <li key={key}>{key}</li> ))} </ul> );}Throws an error if no query instance is found in either the context or the options.
QueryTransition
Section titled “QueryTransition”By default, each useQuery hook creates its own React transition. Use QueryTransition to share a single transition across multiple hooks, preventing visual flickering when related data updates simultaneously:
import { QueryTransition } from "@studiolambda/query/react";import { useTransition } from "react";
function Dashboard() { const [isPending, startTransition] = useTransition();
return ( <QueryTransition isPending={isPending} startTransition={startTransition}> <div style={{ opacity: isPending ? 0.7 : 1 }}> <UserPanel /> <StatsPanel /> <ActivityFeed /> </div> </QueryTransition> );}All useQuery hooks inside QueryTransition use the shared startTransition, so they all participate in the same pending state.
QueryPrefetch
Section titled “QueryPrefetch”Prefetch cache keys when a component mounts:
import { QueryPrefetch } from "@studiolambda/query/react";
function App() { const keys = ["/api/user", "/api/notifications"];
return ( <QueryPrefetch keys={keys}> <Dashboard /> </QueryPrefetch> );}This calls query.query(key) for each key inside a useEffect, warming the cache so child components with useQuery for these keys render instantly without suspending.
QueryPrefetchTags
Section titled “QueryPrefetchTags”Like QueryPrefetch, but also renders <link rel="preload" as="fetch" href={key}> tags for browser-level prefetching:
import { QueryPrefetchTags } from "@studiolambda/query/react";
function App() { const keys = ["/api/user", "/api/posts"];
return ( <QueryPrefetchTags keys={keys}> <Content /> </QueryPrefetchTags> );}The <link> tags hint to the browser to start fetching these resources early, while the useEffect warms the λ Query cache. Any additional <link> attributes can be passed as props.
useQueryPrefetch
Section titled “useQueryPrefetch”The hook version of QueryPrefetch for programmatic prefetching:
import { useQueryPrefetch } from "@studiolambda/query/react";
function App() { const keys = ["/api/dashboard/stats", "/api/dashboard/activity"]; useQueryPrefetch(keys);
return <Dashboard />;}useQueryContext
Section titled “useQueryContext”Access the full context value from the nearest QueryProvider:
import { useQueryContext } from "@studiolambda/query/react";
function DebugInfo() { const context = useQueryContext(); // context includes: query instance, clearOnForget, ignoreTransitionContext}useQueryTransitionContext
Section titled “useQueryTransitionContext”Access the transition context from the nearest QueryTransition:
import { useQueryTransitionContext } from "@studiolambda/query/react";
function TransitionIndicator() { const { isPending } = useQueryTransitionContext();
if (!isPending) return null; return <div className="loading-bar" />;}Suspense and Error Boundaries
Section titled “Suspense and Error Boundaries”Components using useQuery must be wrapped in a <Suspense> boundary (for loading states) and optionally an error boundary (for fetch failures):
import { Suspense } from "react";import { ErrorBoundary } from "react-error-boundary";
function App() { return ( <QueryProvider> <ErrorBoundary fallback={<p>Something went wrong</p>}> <Suspense fallback={<p>Loading...</p>}> <UserCard /> </Suspense> </ErrorBoundary> </QueryProvider> );}After the initial suspension resolves, subsequent updates (refetching, mutations) are handled inside React transitions. The component does not re-suspend — instead, isPending becomes true while the update is in progress.