Server-Side Row Model (SSRM)
When the data lives on the server - millions of rows in a database - the grid
should hold only the page on screen and push sorting, filtering, and paging to
the backend. SvGrid packages this as one datasource contract: you implement
a single async getRows, and createServerDataSource owns the rest.
import { createServerDataSource, type ServerDataSource } from 'sv-grid-community'
const source: ServerDataSource<Row> = {
async getRows({ startRow, endRow, sortModel, filterModel }) {
const res = await fetch('/api/rows', {
method: 'POST',
body: JSON.stringify({ startRow, endRow, sortModel, filterModel }),
})
const { rows, total } = await res.json()
return { rows, rowCount: total }
},
}
let s = $state(/* initial ServerState */)
const ctl = createServerDataSource(source, { pageSize: 50, onChange: (next) => (s = next) })
ctl.refresh()
Wiring to the grid
Run the grid in controlled mode - it records sort/filter UI state but doesn't reorder/slice the data itself (the server already did) - and feed it the controller's current page:
<SvGrid
data={s.rows}
{columns} {features}
sortable filterable
externalSort externalFilter
loading={s.loading}
pageable={false}
onSortingChange={(sorting) => ctl.setSort(sorting)}
onFiltersChange={(f) => ctl.setFilter({ global: f.global, columns: toColumnModel(f.columns) })}
/>
<!-- your pager drives ctl.setPage(...) from s.pageIndex / s.pageCount -->
The controller
createServerDataSource(source, { pageSize, onChange }) returns:
| Method | Does |
|---|---|
refresh() |
Re-fetch the current page (after a mutation). |
setSort(model) |
New sort, jump to page 0, fetch. |
setFilter(model) |
New filter, jump to page 0, fetch. |
setPage(i) |
Fetch page i. |
setPageSize(n) |
New page size, page 0, fetch. |
getState() |
Current { rows, total, loading, pageIndex, pageCount, ... }. |
dispose() |
Stop accepting responses (call on unmount). |
onChange fires on every state transition (including loading: true before a
fetch and loading: false after) so the UI tracks in-flight requests.
Race safety
Each fetch carries a monotonic id; only the latest request is allowed to
land. A slow response for an old sort/filter can't clobber a newer one - the
classic SSRM bug, handled for you. dispose() drops everything in flight.
Notes
- The
ServerRequestcarriesstartRow/endRow(block bounds),pageIndex/pageSize, plussortModelandfilterModel- map these to yourORDER BY/WHERE/LIMIT. - This is the controlled building block; pair it with optimistic updates
(demo 115) or
applyTransactionfor writes.
See the live Server-Side Row Model demo.