demo D287
A feed that survives a reload in React
The routed rows written to the browser’s durable store and read straight back on the next visit, beside a fast feed held to a steady repaint rate so a firehose lands smoothly
router.persist() · router.restore()
This writes the routed rows to the browser's durable store and reads them straight back on the next visit, so a screen built on a live feed is not empty after a reload. Alongside it, a fast feed is held to a steady repaint rate so a firehose lands smoothly.
This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.
The configuration
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.min.css">
<div id="app"></div>
<script type="module">
import React from 'https://esm.sh/react@18';
import { createRoot } from 'https://esm.sh/react-dom@18/client';
import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.esm.min.js';
import { createLatticeReact } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/react.esm.min.js';
import { createDataRouter } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/data-router.esm.min.js';
const { LatticeGrid, useLatticeRouter, LatticeRouterProvider } = createLatticeReact({ React, createGrid, createDataRouter });
const MONEY = { style: 'currency', currency: 'USD', decimals: 0 };
const orderColumns = [
{ field: 'id', title: 'Ref', layout: { width: 110, pin: 'start' } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'value', title: 'Value', type: 'number', format: MONEY, total: 'sum' },
];
const sensorColumns = [
{ field: 'id', title: 'Sensor', layout: { width: 120, pin: 'start' } },
{ field: 'reading', title: 'Reading', type: 'number' },
];
function App() {
const router = useLatticeRouter({ key: 'type', rowKey: 'id' });
React.useEffect(() => {
if (!router) return undefined;
let cancelled = false;
(async () => {
// Write the routed rows to the browser's own durable store, then ask
// for the last session back before any new event arrives. On a first
// visit restore() returns false, so seed; on a reload the orders
// return from storage.
router.persist({ key: 'my-desk' });
const restored = await router.restore();
if (cancelled) return;
if (!restored) router.load([/* rows tagged type: 'order' | 'sensor' */]);
})();
// A steady trickle of new orders, each written through to storage; a
// firehose of sensor updates on the same keys, held to four repaints a
// second (backpressure below) so a firehose lands as a smooth grid.
const orders = setInterval(() => router.apply([{ op: 'upsert', row: { id: 'O-' + Date.now(), type: 'order', region: 'EMEA', value: 500 } }]), 2000);
const sensors = setInterval(() => router.apply([{ op: 'upsert', row: { id: 'S-1', type: 'sensor', reading: Math.random() * 100 } }]), 120);
return () => { cancelled = true; clearInterval(orders); clearInterval(sensors); };
}, [router]);
return (
<LatticeRouterProvider router={router}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '14px' }}>
<LatticeGrid route="order" routeOptions={{ label: 'orders' }} rowKey="id" columns={orderColumns} rows={[]} style={{ height: '340px' }} />
{/* Held to four repaints a second: the updates that arrive in
between are folded into the next one. */}
<LatticeGrid
route="sensor" routeOptions={{ label: 'sensors', backpressure: { maxHz: 4 } }}
rowKey="id" columns={sensorColumns} rows={[]} style={{ height: '340px' }}
/>
</div>
</LatticeRouterProvider>
);
}
createRoot(document.getElementById('app')).render(<App />);
</script>
A live desk that comes back exactly where you left it
A screen built on a live feed usually starts empty and waits for the feed to refill it. Reload the page and the desk you were reading is gone until the next batch arrives. This keeps it. As routed rows arrive they are written to the browser’s own durable store, so when the page loads again the desk comes back exactly where you left it, before a single new event has landed. Reload the tab and the orders are already there.
The same router that fans one feed out to several views is the one doing the saving, so nothing extra sits between the feed and the screen. Name a key to store under and the routed rows are kept up to date as they change; ask for them back on load and they return. Where the browser will not keep durable storage, the router simply runs in memory and fills from the feed as before, so the page still works, it just does not resume.
A fast feed gets its own treatment. Point a route at a firehose and hold its view to a fixed number of repaints a second, and the updates that arrive in between are folded into the next one. The grid stays smooth under a feed that would otherwise make it stutter, and the count of updates merged away tells you how much work the view was spared. One feed, then, that both survives a reload and takes a firehose without flinching.
How do I make a live feed survive a page reload?
Turn on persistence on the data router: call router.persist with a key to store under, and the routed rows are written to the browser’s durable store as they arrive. On the next load, call router.restore before you start the feed, and the views come back filled from storage. For a fast route, pass a backpressure option when you attach its view, capping how often it repaints, and the router coalesces the updates in between into one. Read the coalesced count from the router’s own metrics to see how many updates were merged away.