v2.4.0 brings the 2.4 beta features to the stable @comwit/state channel. You can declare
URL-backed fields in a model and distinguish a query that is still in its initial loading phase
after a chosen threshold. Existing query request and data timing are unchanged.
URL state in ordinary models
searchParam() declares a query-string field in model(). The value is a normal typed model
field: read it with useModel() or a create() domain hook, and assign it in an action. The
Provider manages the browser connection for observed URL models; no separate URL hook is needed.
import { action, model, searchParam, useModel } from '@comwit/state'
const location = model({
threadId: searchParam({ key: 'thread' }), // string | null
page: searchParam({ key: 'page', type: 'number', defaultValue: 1 }), // number
archived: searchParam({ key: 'archived', type: 'boolean' }), // boolean | null
})
const selectionActions = action(({ state }) => ({
select(id: string | null) {
state(location).threadId = id
},
}))
function PageNumber() {
const page = useModel(location, (state) => state.page)
return <span>{page}</span>
}
Valid URL values take priority over defaults. Missing or invalid values use defaultValue, or
null when it is omitted. Empty strings remain valid strings; numbers must be finite and
booleans accept true/false or 1/0. Custom parse and serialize functions support
other formats. A model write uses browser history replace by default; use history: 'push'
when the change should create a back-button entry. Unrelated query parameters and the hash are
preserved.
For server rendering, the optional ComwitProvider prop
getServerSearchParams?: () => string | null provides the request URL before the first model
read and carries that value into hydration. Without it, the first server and hydration snapshots
use defaults; the client reconciles the current URL after commit. Advanced flows can read
searchParam.getSnapshot(state, field) for ready, value, and revision, then use a guarded
searchParam.set() in an action to avoid applying an obsolete asynchronous result. These helpers
do not load route data or validate a route identity; the application still owns those jobs.
See the URL state reference for parsing, server initialization, history, and guarded writes. This feature came from PR #88.
A query flag for slow initial loading
Set slowLoadingMs on a query declaration and read its isSlowLoading flag through the usual
query state. The flag starts false and becomes true only if initial loading continues through
the threshold. It clears on success, error, or usable cached data.
import { model, query, useModel } from '@comwit/state'
const projects = model({
list: query<Project[]>({
initialData: [],
queryFn: fetchProjects,
slowLoadingMs: 500,
}),
})
function Projects() {
const result = useModel(projects, (state) => state.list.load())
if (result.isLoading && !result.isSuccess) {
return result.isSlowLoading ? <ProjectsSkeleton /> : null
}
if (result.isSuccess) return <ProjectsList items={result.data} />
if (result.isError) return <LoadError error={result.error} />
return null
}
The option works with query(), query.infinite(), query.realtime(), local.query(), and
local.infinite(). It accepts finite milliseconds from 0 to 2147483647; omission keeps
the flag false without a timer. It does not delay the request, Promise, data, isLoading, or
isFetching. Fresh local restoration and background revalidation do not show slow initial
loading. An argument or local-scope change starts a new interval, while retries for the same
request keep the existing interval. Server rendering starts no loading timer.
isSlowLoading: false does not imply that data is ready: check isSuccess or other status
flags. The UI still decides whether to reserve space or show a skeleton. See the
query reference and
PR #90.
Upgrade
yarn add @comwit/state@2.4.0
There is no required API migration from 2.3.0. The 2.4 beta declarations and their types are
unchanged when moving to 2.4.0. The deprecated comwit compatibility package remains a
separate package; install @comwit/state for the current API.
The stable package is published under npm's latest tag. The
package README and
LLM references now describe the stable APIs.