Skip to main content
Urbicon UI

Live Updates

Non-disruptive real-time data updates with buffering and user-controlled application.

Overview

Writing rows straight into a filtered, sorted, paged table is disorienting: rows jump position mid-read, disappear behind an active filter, or land in a selection the reader is still building. enableLiveUpdates buffers them instead. A banner above the rows counts what is waiting (“3 new, 2 updated”), and the reader merges it when they are ready.

You push changes from any data source — WebSocket, SSE, or polling — through three methods on the table context: pushInsert, pushUpdate, and pushDelete.

Demo

The panel below plays the role of your backend. Push a few events — the LiveUpdateBanner appears above the rows and counts what is pending. Apply merges the buffer into the table, Dismiss drops it. The demo passes autoApplyOnNavigation={false} so pending changes always wait for your click.

Simulated server feed

Stand-in for a WebSocket/SSE handler — each button calls the corresponding push method.

Order
Customer
Status
Total
Placed
ORD-1001
Aurora Labs
delivered
1240
2026-07-08
ORD-1002
Baltic Trade
shipped
380
2026-07-09
ORD-1003
Cobalt Works
paid
2150
2026-07-10
ORD-1004
Delta Foods
pending
95
2026-07-11
ORD-1005
Ember Studio
paid
640
2026-07-11
ORD-1006
Fjord Analytics
shipped
1780
2026-07-12
ORD-1007
Granite & Co
pending
420
2026-07-12
ORD-1008
Helix Health
pending
310
2026-07-13

Enabling live updates

Enable live updates

One prop. When changes are pending, the LiveUpdateBanner renders automatically between the toolbar and the rows, with Apply and Dismiss actions.
<Table
  {items}
  {columns}
  enableLiveUpdates
/>

Push changes from your data source

onReady hands you the table context from outside the table — pushInsert, pushUpdate, and pushDelete. Wire them to whatever delivers your server events: WebSocket, SSE, or polling.
<script>
  import { Table, type TableContext } from '@urbicon-ui/table';

  let table = $state<TableContext | null>(null);

  $effect(() => {
    const ctx = table;
    if (!ctx) return;
    const socket = new WebSocket('wss://api.example.com/orders');
    socket.onmessage = (event) => {
      const message = JSON.parse(event.data);
      if (message.type === 'order:created') ctx.pushInsert(message.order);
      if (message.type === 'order:updated') ctx.pushUpdate(message.id, message.changes);
      if (message.type === 'order:deleted') ctx.pushDelete(message.id);
    };
    return () => socket.close();
  });
</script>

<Table
  {items}
  {columns}
  enableLiveUpdates
  onReady={(context) => (table = context)}
/>

Alternative: a feed component inside the table tree

If the feed is its own component, it can call getTableContext() instead — that resolves through component context, so it must render inside the table; the toolbar snippet is the natural mount point. Re-add SmartFilterBar to keep the default search toolbar.
<script>
  // LiveFeed.svelte calls getTableContext() — so it must render inside the table
  import { Table, SmartFilterBar } from '@urbicon-ui/table';
  import LiveFeed from './LiveFeed.svelte';
</script>

<Table {items} {columns} enableLiveUpdates>
  {#snippet toolbar()}
    <LiveFeed />
    <SmartFilterBar />
  {/snippet}
</Table>

Buffer & merge semantics

The buffer holds at most one pending outcome per row, so a busy feed cannot make it grow past the number of rows it touched. A second pushInsert replaces the first, consecutive pushUpdate calls merge into one, a pushDelete for a row that is still a pending insert cancels both, and a pushUpdate for one is folded into the insert so the row lands already updated. Order of arrival does not matter.

Applying runs deletes, then updates, then inserts. A row that just took an update is highlighted for three seconds, so the change is findable in a long list, and a deleted row leaves the selection along with the table. An update or delete naming a row the table does not hold is skipped, with a warning in dev.

Auto-apply on navigation

With autoApplyOnNavigation (default true), pending changes are merged automatically when the user changes page, sort, filter, or search — the view is reorganizing anyway, so merging at that moment is non-disruptive. Set it to false to make the banner the only way changes are applied.

Explicit apply only

Pending changes are never merged implicitly — the user must click Apply (or you call applyAllUpdates() on the context).
<Table
  {items}
  {columns}
  enableLiveUpdates
  autoApplyOnNavigation={false}
/>