Requests
Requesting Lists
The useRequest hook can be used to declare our data needs for a specific screen or component tree. At the root of our app, we can request a list of posts like this:
import { useRequest } from 'react-fate';
import { PostCard, PostView } from './PostCard.tsx';
export function App() {
const { posts } = useRequest({ posts: { list: PostView } });
return posts.map((post) => <PostCard key={post.id} post={post} />);
}This component suspends or throws errors, which bubble up to the nearest error boundary. Wrap your component tree with ErrorBoundary and Suspense components to show error and loading states:
<ErrorBoundary FallbackComponent={ErrorComponent}>
<Suspense fallback={<div>Loading…</div>}>
<App />
</Suspense>
</ErrorBoundary>NOTE
useRequest may issue multiple operations in the same render pass. fate transports can batch those operations into fewer network requests: the native HTTP transport batches same-microtask operations into one POST /fate request, and the tRPC adapter can use tRPC's HTTP Batch Link.
Requesting Objects by ID
If you want to fetch data for a single object instead of a list, you can specify the id and the associated view like this:
const { post } = useRequest({
post: { id: '12', view: PostView },
});If you want to fetch multiple objects by their IDs, you can use the ids field:
const { posts } = useRequest({
posts: { ids: ['6', '7'], view: PostView },
});Other Types of Requests
For any other queries, pass only the type and view:
const { viewer } = useRequest({
viewer: { view: UserView },
});Request Arguments
You can pass arguments to useRequest calls. This is useful for pagination, filtering, or sorting. For example, to fetch the first 10 posts, you can do the following:
const { posts } = useRequest({
posts: {
args: { first: 10 },
list: PostView,
},
});Request arguments are part of the cache key. Two list requests for the same root with different filters or sorting arguments keep separate list state, and cursor arguments are merged into the same list when you load more pages. The selected view is part of the key as well: requesting PostCardView and PostDetailView can share normalized records, but fate still tracks whether the specific fields for each request are present.
Request Modes
useRequest supports different request modes to control caching and data freshness. The available modes are:
cache-first(default): Returns data from the cache if available, otherwise fetches from the network.stale-while-revalidate: Returns data from the cache and simultaneously fetches fresh data from the network.network-only: Always fetches data from the network, bypassing the cache.
You can pass the request mode as an option to useRequest:
const { posts } = useRequest(
{
posts: { list: PostView },
},
{
mode: 'stale-while-revalidate',
},
);Cache Lifetime
fate stores records in a normalized cache keyed by __typename and id. Lists and root queries point at those records, and views read from the normalized cache. When a useRequest call is mounted, fate retains the request so the records and lists needed by that screen stay in memory. When the component unmounts, the request is released and fate schedules garbage collection.
Released requests are kept in a small release buffer before their data becomes collectible. This makes common route transitions cheap: navigating away from a screen and quickly coming back usually reuses the cached records instead of refetching them. The default release buffer stores the 10 most recently released requests.
You can tune the buffer when creating the client:
const fate = createClient({
gcReleaseBufferSize: 20,
roots,
transport,
types,
});Set gcReleaseBufferSize to 0 in tests or very memory-sensitive environments when released screens should be collected immediately.
cache-first request handles are stable while their request is cached. If garbage collection later removes the data for a fulfilled request, the next cache-first request automatically fetches it again rather than returning stale references.
If you call fate.request(...) outside React and need the result to stay in memory across manual gc() calls, retain the same request for the lifetime of that work:
const request = { posts: { list: PostView } };
const retained = fate.retain(request);
try {
const { posts } = await fate.request(request);
// Use posts while this request is retained.
} finally {
retained.dispose();
}Garbage collection waits for active optimistic updates to settle before sweeping records. This keeps temporary optimistic records and their list positions stable while mutations are still pending.