Data
Choose resources, queries, mutations, and actions by ownership and consistency needs.
@askrjs/askr0.0.59
How to use data
Define the cache key and cancellable fetch contract once, then create the query where a component needs the result.
- Import the published @askrjs/askr entrypoint.
- Keep data configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { createQuery, defineQuery } from '@askrjs/askr/data';
const project = defineQuery({
key: ({ id }: { id: string }) => 'project:' + id,
fetch: ({ id, signal }) => api.projects.get(id, { signal }),
});
const result = createQuery(project, { id: projectId });Primitive selection
Askr splits data handling into four primitives instead of one do-everything hook: plain state() for values only a component cares about, resource() for one-off async work tied to a component's lifecycle, createQuery()/defineQuery() for server data shared across the app, and createMutation()/action() for writes. Picking the right one comes down to two questions: does more than one part of the tree need this data, and does a write need to invalidate it elsewhere? If the answer to both is no, reach for resource() or plain state() before anything from the data module.
Local lifecycle work
resource() from @askrjs/askr/resources runs an async function scoped to wherever you call it, and re-runs whenever its dependency array changes, the same way an effect dependency list works. It returns pending, error, and value fields directly, so a component can branch on them without extra wiring. Because the fetch function is passed a signal, in-flight work is cancelled automatically when dependencies change or the component unmounts.
Shared server data
Once two or more components need the same server data, a resource() call per component means duplicate requests and no shared cache. createQuery() and defineQuery() solve that by keying data on a string derived from the input, so repeated calls with the same key reuse the same cached entry through a DataRuntime. Consistency is tracked explicitly through states like fresh, stale, refreshing, and pending-write rather than a single boolean loading flag.
Writes and invalidation
Writes go through createMutation(), which wraps an async action function and exposes execute(), abort(), and reset() alongside a status field (idle, pending, success, or error). A mutation's affects() option maps its input and result to the query key prefixes it should invalidate, and invalidate() or queryScope().invalidate() can also be called directly wherever a write happens outside a mutation.