Skip to content

raf-query is early. The API can still change between minor releases, so use it at your own risk.

raf-query

Guides

What belongs in the cache

raf-query caches asynchronous data from a backend or the browser. What that leaves for URL state, local state and a client store.

raf-query is a cache for asynchronous data: anything you load with a promise, that can go out of date, and that many components may need. Most of that is data a backend owns — posts, users, comments. It also fits work only the browser can do: a push subscription, a notification permission, a service worker. Their handles are built in the browser after hydration, so they never run during SSR, and they use the same factories, handles, invalidation and readers as everything else.

It's not a general-purpose state manager for synchronous UI state.

Once async data lives in the cache, most apps find that what's left is small:

StateWhere it belongs
Posts, users, comments, anything from a backendraf-query
Push subscription, permission, service worker, media queryraf-query, browser-only
Filters, sort, pagination, the selected tabThe URL: search params and the path
An input's text, whether a menu is openuseState in the component
Pending and optimistic state of a writeuseActionState, useOptimistic
Theme, a draft that survives navigationA small client store, or localStorage

The URL deserves special mention. raf-query asks that anything which changes what a query fetches lives in the URL, so the loader can start the query on navigation. That's also what makes a page shareable, bookmarkable and restorable with the back button, which state in a store isn't.

Copying query data into a store

Avoid copying query data into a client store (or into useState). The copy doesn't refetch, doesn't hear about invalidation, and goes stale the moment the cache updates. Read the query wherever the data is needed: requests are deduped, and readers share one cache entry.

The exception is a form: initializing an input from query data is fine. defaultValue={post.title} gives the user their own copy to edit, and the write goes through an action.