Askr
Routing & Data

Choosing a Primitive

Use choosing a primitive with the current published Askr packages.

  • @askrjs/askr0.0.59

How to use choosing a primitive

Define the cache key and cancellable fetch contract once, then create the query where a component needs the result.

  1. Import the published @askrjs/askr entrypoint.
  2. Keep choosing a primitive configuration next to the component, route, or server composition root that owns it.
  3. 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 });

State

state() returns a getter/setter tuple — call the getter to read the current value, call the setter to update it — and it never touches the network. It's the right tool for anything that lives and dies with the component: form input values, toggle flags, a selected tab index. There's no caching, no key, and no invalidation to think about, which is exactly the point.

Resource

resource(fn, deps) is for async work that belongs to one place in the tree and doesn't need to be shared or revalidated from elsewhere — fetching detail for the record a single page is showing, for example. It gives you pending/error/value plus a refresh() function, and cancellation is handled for you through the AbortSignal passed into fn. If you find yourself calling the same resource() logic in two components and wishing they shared a cache, that's the signal to move to a query instead.

Query

A query is what a resource becomes once other parts of the app need the same data, or once a mutation elsewhere needs to invalidate it. createQuery() and defineQuery() key data by a string derived from the input, and the returned Query<T> exposes named consistency states — fresh, stale, refreshing, pending-write — instead of a single loading flag. Reach for a query when caching, sharing, or targeted invalidation matter; reach for a resource when they don't.

Action or mutation

createMutation() is for writes triggered from code you control directly: pass it an action function and an optional affects() mapping, and call execute() when you're ready. action() and defineAction(), from @askrjs/askr/actions, are for writes that originate from a form — defineAction() declares an id, an input schema, and the query prefixes it invalidates, and ActionForm binds a real HTML form to that declaration with validation built in. Use a mutation when you're calling code yourself; use an action when you want a form to submit directly against a validated contract.