Gonvex

LiveGrid

Build large realtime data grids without subscribing to an entire table.

LiveGrid

LiveGrid is the Gonvex pattern for large interactive tables.

Normal useQuery works well for result sets that fit comfortably in memory. A grid with thousands or millions of rows needs a different shape: subscribe only to the visible window, keep rendering synchronous, and refresh visible data when relevant writes commit.

Use Case

LiveGrid is meant for surfaces like:

  • task tables
  • CRM records
  • audit logs
  • inventory grids
  • admin data browsers
  • scheduling boards

Define A LiveGrid Function

func Register(app *gonvex.App) {
  app.LiveGrid("tasks.grid", TasksGrid)
}

type TasksGridArgs struct {
  Offset  int      `json:"offset"`
  Limit   int      `json:"limit"`
  Sort    []Sort   `json:"sort,omitempty"`
  Search  string   `json:"search,omitempty"`
  Columns []string `json:"columns,omitempty"`
}

func TasksGrid(ctx *gonvex.QueryCtx, args TasksGridArgs) (gonvex.GridResult[TaskRow], error) {
  if ctx.User == nil {
    return gonvex.GridResult[TaskRow]{}, errors.New("authentication required")
  }

  return queryTaskGrid(ctx.Context, ctx.DB, ctx.TenantID, args)
}

Frontend Pattern

The grid component tracks its visible row window and subscribes to that window:

const visible = getVisibleWindow();

client.subscribeQuery(api.tasks.grid, {
  offset: visible.offset,
  limit: visible.limit,
  sort,
  search,
  columns: visibleColumns,
}, onResult);

When a result arrives, replace the corresponding range in the in-memory row cache.

Why A Row Cache Exists

Grid libraries usually ask for cell content synchronously. Rendering cannot wait for a network request inside getCellContent.

The recommended shape is:

visible window changes
-> subscription args change
-> runtime returns rows
-> frontend updates in-memory row cache
-> grid renders synchronously from cache

Offscreen cached rows are a performance optimization. They do not need to stay live. When the user scrolls back, that range becomes visible and subscribes again.

Avoid Request Storms

Large grids can generate many scroll/search/sort events.

Recommended controls:

  • bucket scroll offsets into page windows
  • add small overscan/padding around the visible rows
  • debounce text search input
  • cancel or replace older subscriptions when the window changes
  • request only visible columns when possible
  • use estimated counts where exact counts are expensive

Realtime Behavior

The active visible window should be correct.

visible subscribed rows = source of truth
offscreen cached rows = stale-allowed cache

If a mutation changes visible rows, the subscription should refresh. If a mutation changes only offscreen rows, it can wait until those rows become visible again.

Counts

LiveGrid result shapes should support count modes:

none
  fastest, no total row count

estimate
  cheap approximate count for scrollbars

exact
  exact count when the user or UI needs it

Future Enhancements

The first LiveGrid protocol can send full page results. Later improvements can add:

  • row-level patches
  • cell-level patches
  • server-side computed columns
  • persisted browser cache
  • optimistic row edits
  • column permission filtering
  • tenant-aware cache namespaces

On this page