Live updates
Poll a cheap version stamp; re-fetch only the visible window when it moves. The
grid already fetches O(viewport), so a live grid costs one aggregate query per
poll plus one page fetch per actual change.
// Declare what stamps the version. Without this the stamp is a row count, which
// sees inserts and deletes but not in-place edits.
Grid::table('sales')->key('id')
->columns(['sku', 'region', 'status'])
->column('revenue', type: 'number')
->column('updated_at', type: 'date')
->versionColumn('updated_at');
class SalesGrid extends DataGrid
{
public bool $liveUpdates = true;
public int $livePollSeconds = 5;
}
// Vue / Svelte / headless
createGrid({ columns, fetch, liveUpdates: { intervalMs: 5000 } });
What the stamp is
meta: ['version'] returns dataVersion, built from COUNT(*) and
MAX(versionColumn) over the same filter and search as the request.
The count is not decoration. MAX(updated_at) on its own cannot see a DELETE —
the maximum is unchanged, so a removed row would sit on screen until something
else happened to bump the column. The count catches inserts and deletes; the
maximum catches in-place updates.
| Change | MAX alone |
COUNT + MAX |
|---|---|---|
| in-place update | ✅ | ✅ |
| insert | ✅ | ✅ |
| delete | ✖ | ✅ |
| delete + insert between two polls, same maximum | ✖ | ✖ |
| write that does not touch the version column | ✖ | ✖ |
So it is a heuristic: a cheap "probably changed" signal, not a correctness
guarantee. The worst case is being briefly stale until the next real change, which
is the right trade for a grid. If you need exactness, drive revalidate() from a
broadcast event instead of polling.
Without ->versionColumn(...) the stamp is just the count: inserts and deletes
show up, in-place edits do not.
What it costs
One aggregate query per poll. take: 0 means no rows come back, so a poll is not
a page fetch.
It is not free on a large filtered table: the stamp is an aggregate over the
filtered set, so an unindexed version column is a scan per tick. Index it, and
index it together with whatever the grid filters on — the same reasoning that
makes total opt-in applies here.
Polling stops while the browser tab is hidden (whileHidden: true opts back in),
and never fires while a row is being edited — refreshing under someone's cursor
would discard what they are typing. The change is picked up on the next poll after
they finish.
Push instead of poll
revalidate() is the same code path the timer uses, so a broadcast event can
drive it directly and you can leave polling off:
const grid = createGrid({ columns, fetch }); // no liveUpdates
Echo.private(`sales`).listen("SalesChanged", () => {
void grid.revalidate(); // checks the stamp, refetches if it moved
});
The package does not ship the transport. Broadcasting means your application emitting events on writes, which it has to do for its own tables — there is nothing generic for a package to do there.
One caveat worth knowing
The viewport is paged with skip/take. If rows are inserted or deleted above
the current page, the same offset points at a slightly different slice, so a live
grid can appear to jump by a row. That is inherent to offset paging under
concurrent writes, not something the version stamp introduces.