query()
Declares a client-side data fetching field inside a model.
import { query } from 'comwit'
Basic usage
import { model, query } from 'comwit'
export const post = model<PostState>({
posts: query<Post[]>({
initialData: [],
queryFn: () => api.post.findAll(),
}),
})
The type in your state should be Query<Data> (or Query<Data, Arg> if the queryFn takes an argument):
import { Query } from 'comwit'
export type PostState = {
posts: Query<Post[]>
comments: Query<Comment[], string> // string = queryFn arg type
}
Methods
Once accessed via state() in an action, query fields expose these methods:
| Method | Description |
|---|---|
.query(arg?) | Fetch data. Respects staleTime — skips if data is fresh. |
.refetch() | Force re-fetch with the last used argument. |
.set(data) | Manually set data (for optimistic updates). |
Status flags
| Flag | Type | Description |
|---|---|---|
.data | T | The current data |
.isLoading | boolean | True on first fetch (no prior success/error) |
.isFetching | boolean | True during any fetch |
.isSuccess | boolean | True after a successful fetch |
.isError | boolean | True after a failed fetch |
.error | string | null | Error message if failed |
Options
Pass options at the query definition or globally via ComwitProvider:
| Option | Description |
|---|---|
staleTime | Time in ms before cached data is considered stale (default: 0) |
cacheTime | Alias for gcTime |
gcTime | Time in ms before unused cache entries are garbage collected |
placeholderData | Data to show while loading. Use keepPreviousData to retain previous results. |
force | Skip cache and always fetch (call-time only) |
queryFn context
queryFn receives (arg, context) where context.state is a readonly snapshot of the current query state:
posts: query<Post[]>({
initialData: [],
queryFn: (_, { state }) => {
// state.data, state.isLoading, etc.
return api.post.findAll()
},
})
query.infinite()
For infinite scroll / pagination. Use Query.Infinite<T> as the type.
import { Query } from 'comwit'
// types.ts
export type PostState = {
trending: Query.Infinite<Post[]>
}
// model.ts
import { query } from 'comwit'
export const post = model<PostState>({
trending: query.infinite<Post[]>({
initialData: [],
queryFn: (_, { state }) => api.post.trending(state.cursor),
}),
})
Additional methods
| Method | Description |
|---|---|
.nextFetch() | Fetch the next page. No-op when hasMore is false. |
.previousFetch() | Go back to the previous cursor. |
Additional flags
| Flag | Type | Description |
|---|---|---|
.cursor | string | null | Current pagination cursor |
.hasMore | boolean | Whether more pages exist |
Usage in actions
async loadMoreTrending() {
await this.model.trending.nextFetch()
}
The queryFn return value can include cursor and hasMore to control pagination:
queryFn: async (_, { state }) => {
const res = await api.post.trending(state.cursor)
return {
data: res.posts,
cursor: res.nextCursor,
hasMore: res.hasNext,
}
}
query.realtime()
For real-time data with WebSocket/SSE subscriptions. Use Query.Realtime<T> as the type.
After the initial queryFn fetch succeeds, a subscribe function is called with callbacks to push live updates.
import { Query } from 'comwit'
// types.ts
export type ChatState = {
messages: Query.Realtime<Message[]>
}
// model.ts
import { query } from 'comwit'
export const chat = model<ChatState>({
messages: query.realtime<Message[]>({
initialData: [],
queryFn: (roomId) => api.chat.history(roomId),
subscribe: (callbacks) => {
const ws = new WebSocket(`wss://api.example.com/chat`)
ws.onmessage = (e) => {
callbacks.update((prev) => [...prev, JSON.parse(e.data)])
}
ws.onopen = () => callbacks.onStatus('connected')
ws.onclose = () => callbacks.onStatus('disconnected')
ws.onerror = (e) => callbacks.onError(e)
return () => ws.close()
},
}),
})
Subscribe callbacks
The subscribe function receives an object with these callbacks and must return a cleanup function:
| Callback | Signature | Description |
|---|---|---|
update | (updater: (prev: T) => T) => void | Merge data using updater function |
set | (data: T) => void | Replace data entirely |
refetch | () => void | Re-execute the queryFn |
onStatus | (status: ConnectionStatus) => void | Update connection status |
onError | (error: unknown) => void | Set error state |
Additional methods
| Method | Description |
|---|---|
.unsubscribe() | Stop the live subscription. Sets status to 'disconnected'. |
Additional flags
| Flag | Type | Description |
|---|---|---|
.connectionStatus | ConnectionStatus | 'connecting' | 'connected' | 'disconnected' | 'reconnecting' |
.isConnected | boolean | True when status is 'connected' |
Usage in actions
async connectChat(roomId: string) {
await this.model.messages.query(roomId) // fetches + starts subscription
}
disconnect() {
this.model.messages.unsubscribe()
}