createQuery allows you define a fetcher factory that is auto-fetched and cached with or without an Accessor value. This creates a streamlined and high-performant
way to fetch data within your application with less effort.
The value of a query is inteded to be consumed by useQuery.
A query is just a fancy wrapper that subscribes to the signals Observer. This means that it can be used for any scenario that is Promise-based - not just data-fetching. If you have a Promise, it can be a query.
See the features table to understand how to best utilize queries for each scenario you may have.
Note
Queries are compatible with React Suspense and Error Boundaries.
Creating a query requires two things:
Ultimately, when you create a query you are really just creating a factory that can be called where ever and whenever you are ready.
Depending on your goals will determine how to use the query. The following sections outline the different use cases depending on whether you are fetching data within a component or outside of one.
There are no restrictions to Cerberus Signal Primitives which means you can use them at any scope.
createQuery only defines a query factory and does not actually call it. To use the
query in a component you must pass the returned value into the useQuery hook which re-runs when the arguments provided change.
The value returned is the result from calling the signal-tracked Accessor. This
is equivalent to the return value from the useRead hook.
There are two different patterns you can use depending on your fetching goals outside of components.
If you just want to trigger the fetch and wait for the result in a standard async function, you can simply await the Promise attached to the pending state.
If you want a vanilla JS function to automatically react to the data whenever it arrives (or whenever it is invalidated and refetched in the background), you drop it into a createEffect.
This is incredibly useful for syncing data to vanilla DOM elements, Web Components, or external non-React stores.
Cerberus queries also support streaming data via Async Generators. This is powerful if you are using an LLM API or your local API supports data streaming.
In an SSR environment, you execute the factory's raw fetcher directly, bypassing the reactive cache. Then, you pass that data down to your Client Components to "hydrate" or seed the Cerberus cache, ensuring the client doesn't double-fetch on mount.
This is the standard SSR pattern for React.
Expose the raw, stateless fetcher function from your factory. You simply await it like a standard asynchronous function.
On the client side, use the initialData property. If the Cerberus cache is empty, it will instantly seed the cache with the server's data, skipping the <Suspense> boundary entirely.
Queries have a built-in optimistic store when combined with Mutations. This means the UI will update in "real-time" while the query will fetch in the background and eventually overwrite the optimistic store.
Mutations are only necessary if you are writing updates. If your query is only intended to be read-only, it will automagically fetch when the params change.
| Feature | With Signal | Without Signal |
|---|---|---|
| auto-intial fetch | ✅ | ✅ |
| auto-refresh fetch | ✅ | ✅ |
| caching | ✅ | ✅ |
| requires mutation | ❌ | ❌ |
createQuery accepts the following options:
| Params | Required | Description |
|---|---|---|
Promise<any> | true | The Promise to call when activated. |
key | true | A unique key used for caching queries. |
createQuery returns the value of the Promise along with some additional properties:
| Property | Type | Description |
|---|---|---|
key | UUID | A uniqe ID to be referenced in the mutation invalidate Array. |
currentArgs | Args | The args passed into createQuery stored in a reference. |
Note
The query.key is SHA generated from the contents of the query parameters. This means, if you have multiple signal-like queries, they should each have unique return values or else they will be synced together.
createQuery accepts two arguements to strictly type the return result <CacheNode, Args>. These are naturally deferred through our strict type chain. This means you don't need to worry about it outside of rare use cases.
However, we do recommend using this if you are creating a query that doesn't expect any arguments: createQuery<string, void>(...).
{
"id": "bf5cd346-0ebd-489f-ad0f-923dc02e27b1",
"name": "User bf5cd346-0ebd-489f-ad0f-923dc02e27b1"
}