v2.1.0 — Selector-owned query loading
Selector-owned query loading
React components can now declare a query and its typed argument directly inside the existing useModel() / create() selector:
const products = useProduct((state) =>
state.products.load({ page, filter })
)
const stats = useProduct((state) => state.stats.load())The argument is inferred from Query<Data, Arg>. Calling .load() is the explicit opt-in; selecting the resource without it remains a passive read and never starts a request.
Behavior
- reports first-load state during the initial render, avoiding the empty → skeleton flash before an effect runs
- starts the request after React commits
- changes cache keys when the serialized argument changes
- does not restart a stable mounted query on unrelated rerenders
- deduplicates concurrent selector loads for the same resource and key
- supports single, infinite, and realtime resources
- keeps call-time query options in imperative actions
Fully backward compatible
Existing action methods remain unchanged:
await this.model.products.query({ page, filter })
await this.model.products.refetch()
this.model.products.set(serverData)useEffect-driven loading continues to work. Use selector .load() for view-owned requests and action .query() for commands, preloads, forced requests, multi-query workflows, and side effects. No additional React hook was added.
Documentation
- rewrote
llms.txtas a concise English domain implementation guide - documented selector-owned versus command-owned query loading
- added cache-aware App Router Suspense fallback guidance using existing
silent()+.set()initialization instead of a new hydration API - added the v2.1.0 release article
Validation
- 25 test files, 345 tests passed
- TypeScript and selector type-contract checks passed
- ESM/CJS bundle and declaration generation passed
- docs production build passed
- published package tarballs were inspected
Packages
@comwit/state@2.1.0comwit@2.1.0compatibility shim →@comwit/state@^2.1.0