# Reading the state object `useTableCollection` hands you > **Scope.** The [Component Docs Gate](../../SKILL.md) covers component *names and props*, and the > package's own `TableState` declaration is the member list. This file covers only what that > declaration cannot tell you: the plausible members that are absent from it, and the behaviour > behind the ones that are there. ## Why guessing here always fails `useTableCollection()` returns a `TableState`. For the members themselves, read the declaration the index names: `patterns-lookup.cjs TableState`, alongside the other names you need, then the file it prints. Reading them is not the hard part; the hard part is that several plausible members don't exist at all, and one that does means something other than its name suggests (see [Four that bite in practice](#four-that-bite-in-practice)). **One thing not to conclude from the docs bundle.** `dist/dts-bundle/` publishes `CollectionState` as a stub: ```ts declare class CollectionState { [key: string]: unknown } ``` That is a documentation abridgement, not the declaration your code compiles against. `tsc` resolves `dist/types/index.d.ts`, which re-exports the **fully typed** `CollectionState` from `@wix/bex-core` — no index signature. So `state.collection.…` is properly typed, a wrong member is a compile error at the access, and reading `state.collection.status.status` to debug a stuck table is sound. See `/dist/docs/Reading the Doc Indices.md` for what a stub means. Prefer a typed member on `TableState` itself anyway — it is the object the hook hands you, and the table's own view of the collection. ## Four that bite in practice **`state.filters` does not exist.** Filter state lives on the filter objects you created with `stringsArrayFilter()` / `dateRangeFilter()` and passed into `filters`. Keep those in module-level consts and reference the const in both the hook config and the JSX — that is also what `MultiSelectCheckboxFilter`'s `filter` prop wants. **`showLoadingState` is not "the query is running".** It stays `true` when the query resolves with zero rows, so binding `SummaryBar`'s `status` to it leaves the bar on skeleton pills permanently on an empty collection — which reads as a hung network rather than an empty result. Use `state.showErrorState ? 'error' : 'success'`. **`collection.isLoading` and `collection.refresh()` are not members.** They look plausible, they compile against the index signature, and neither exists at runtime. ### A `fetchTotal` that resolves undefined shows 0, not the rows This is the "`SummaryBar` says 0 while the table under it is full" report, and it is worth tracing once because every step is silent. `FetchTotalState` wraps your function in a second, one-item collection whose page is literally `{ items: [], total: await fetchTotal(query) }`. That page then goes through `CollectionState.updateResult`, which computes `lastPage.total ?? lastPage.items.length` — so a `total` of `undefined` becomes `0`, because the synthetic page has no items to fall back on. And `QueryResultState.total` reads `fetchTotal ? fetchTotal.total : this._totals.processed`: **the moment `fetchTotal` exists it wins, and the loaded-rows fallback is gone.** Two consequences worth keeping in mind: - **A wrong `fetchTotal` is worse than no `fetchTotal`.** Omit it and `total` degrades to the rows you have; wire it to something that resolves `undefined` and a page with 4,000 matching rows reports `0`. Nothing throws, nothing fails `tsc`, and the table beside it is correct. - **`pagingMetadata.total` is not a count.** A cursor-paged response carries no total, and `@wix/data` documents the field as returned only under *offset* paging with `returnTotalCount: true`. Under the cursor paging these pages default to, expect `undefined`. Call something that counts — [QUERY_AND_PAGING.md](QUERY_AND_PAGING.md#what-fetchtotal-is-allowed-to-call). To tell the two apart in a running page, show `state.collection.totalStatus` next to the number: it is the totals collection's own status, so a `success` beside a `0` means your function resolved nothing, not that the query failed. ## Reading state outside the table: it is MobX `@wix/patterns` depends on `mobx` and `mobx-react-lite`, so the state object is observable and a plain React component does not track it. Anything you derive from `state` in your own component — a `SummaryBar` count, a badge total, a header figure — is computed once on first render, when the collection is still empty, and never recomputed. The table itself updates because it observes internally; your derived numbers sit at zero next to a table full of rows, which reads as a bug in the query rather than in the wiring. `useSelector` from `@wix/patterns` subscribes: ```tsx const loadedCount = useSelector(() => state.keyedItems.length); const rows = useMemo(() => state.keyedItems.map((k) => k.item), [loadedCount]); ``` **Select a primitive.** Returning a fresh array or object from the selector gives it a new identity on every evaluation. Select a length, a total or an id, then derive the rest once it fires. ## Query and result shapes `fetchData(query)` receives a `ComputedQuery`: `limit`, `offset`, `page`, `search`, `rawSearch`, `cursor`, `filters`, `rawFilters`, `sort`. **Read the return type from `@wix/bex-core`, not from `@wix/patterns`.** The name `CursorQueryResult` exists in both packages and they are different types. The one your `fetchData` must satisfy is the generic in `node_modules/@wix/bex-core/dist/types/hooks/paginationModeConfig.d.ts`: ```ts interface DataResultRaw { items: T[]; total?: number | null; available?: number; // total BEFORE filters cursor?: string | undefined | null; hasNext?: boolean | null; } type OffsetQueryResult = Omit, 'cursor'>; type CursorQueryResult = Omit, 'cursor'> & { cursor: string | undefined | null }; ``` Three consequences, each of which has shipped as a bug: **In cursor mode the `cursor` key is required; its value may be `undefined`.** The intersection re-declares `cursor` without `?`, so `return { items }` does not compile — but `{ items, cursor: undefined }` does. "Required" is about the key, not the value. **On the last page return `undefined`, never `''`.** An empty string is still a cursor to the collection: it requests the next page forever and appends the same rows each pass, a table that grows without end while the API is perfectly happy, rendering as a spinner under the last row that reads as "still loading". Derive it so the empty case collapses: ```ts cursor: response.pagingMetadata?.cursors?.next || undefined, ``` **`hasNext` and `total` exist in both modes** — they are on `DataResultRaw`, which both aliases extend. Cursor mode also accepts a separate `fetchTotal`, since a cursor-paged response carries no total; build its filter exactly as the page's or the count disagrees with the rows it counts. Look any of these up yourself, all in one `patterns-lookup.cjs` call ([WIX_PATTERNS_DOCS.md § 1](../WIX_PATTERNS_DOCS.md#1--the-index)), and read the file it names. The index is the single source of truth, and it moves between versions.