# Choosing a Primitive

> When to reach for a resource, a query, or a plain loader — and what each one owns.

Source: [https://askrjs.com/docs/data/choosing-a-primitive](https://askrjs.com/docs/data/choosing-a-primitive)

Status: stable. Packages: @askrjs/askr.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

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

```tsx
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&lt;T&gt; 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.

## Documentation navigation

[Previous](https://askrjs.com/docs/data/index.md) | [Next](https://askrjs.com/docs/data/resources/index.md)
