Server data now initializes before the first snapshot
v2.3.0 adds useDomain.hydrate(entries) to hooks created by create(). A small Client Component
route adapter can initialize resolved Server Component data before the actual UI reads its first
useSyncExternalStore snapshot.
// Server Component
const initialPost = await serverApi.findBySlug(slug)
return <BlogRoute slug={slug} initialPost={initialPost} />
// Client route adapter
function BlogRoute({ slug, initialPost }: Props) {
useBlogSocial.hydrate({
post: { arg: slug, data: initialPost },
})
return <BlogArticle />
}
// Actual UI
function BlogArticle() {
const post = useBlogSocial((state) => state.post.data)
return post ? <Article post={post} /> : null
}
The route adapter is the only component that receives the resolved server value. Business UI keeps reading the normal domain hook and does not need duplicate data props.
Typed, complete query entries
hydrate() infers the accepted fields and every arg and data type from the model. It records
freshness internally, selects the serialized argument as active, and creates a complete successful
query entry with loading and fetching disabled. null and undefined inputs are safe no-ops.
The initializer supports query(), query.infinite(), local.query(), and local.infinite().
Hydrated local query entries reconcile canonical entities and persist to IndexedDB after commit.
Realtime and standalone local() resources are intentionally excluded.
Equivalent hydration for the same provider, field, key, and data is idempotent. New unread entries can initialize before their first snapshot. Updates to an already observed entry wait for the requesting render to commit, so an abandoned route transition cannot overwrite the currently visible state. A hydrated value also invalidates older pending requests for that key.
Why silent() was not enough
silent() suppressed subscriber callbacks but did not suppress React external-store snapshot
checks. Mutating a model after useSyncExternalStore had read it could make React 19 discard and
retry the uncommitted render repeatedly under Next.js Cache Components and partial prerendering.
silent() is now deprecated and must not be used for render-time hydration. The new hydration path
does not invoke an action, the public proxy, .set(), or silent() during render.
Query loading and experimental Suspense
Selector .load(arg) remains the default non-suspending client query path and still starts work
after commit. Selector .suspend(arg) remains available as an experimental render-time query path
for isomorphic HTTP query functions. It accepts no Promise or initial-data override. Next.js Server
Functions must run in a Server Component and pass their resolved value through hydrate().
The descriptor-level suspense option and Query.Suspense* aliases remain deprecated compatibility
APIs.
Upgrade
yarn add @comwit/state@2.3.0