Skip to main content

Load data on demand with fetch

For data sets that live on a server, size the data array to the total number of records up front so the scroll bar is right, fill it with placeholder rows, and load a page whenever the user scrolls near rows that have not been fetched. scrollIndexRect tells you which rows are in view.

import canvasDatagrid from 'canvas-datagrid';

const app = document.getElementById('app');
const gridElement = document.createElement('div');
gridElement.style.height = '400px';

const total = 200; // the API has 200 records
const pageSize = 20;
const loading = 'Loading...';

const data = Array.from({ length: total }, (_, id) => ({
  id: id + 1,
  title: loading,
  completed: '',
  loaded: false,
}));
const pending = new Set<number>();

const grid = canvasDatagrid({
  parentNode: gridElement,
  data,
  editable: false,
  schema: [
    { name: 'id', width: 60 },
    { name: 'title', width: 420 },
    { name: 'completed', width: 100 },
  ],
});
grid.style.height = '100%';
grid.style.width = '100%';

async function loadPage(page: number) {
  if (pending.has(page)) return;
  pending.add(page);
  const start = page * pageSize;
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/todos?_start=${start}&_limit=${pageSize}`,
  );
  const rows = await response.json();
  rows.forEach((row: any, index: number) => {
    const target = data[start + index];
    target.title = row.title;
    target.completed = row.completed ? 'yes' : 'no';
    target.loaded = true;
  });
  grid.draw();
}

function loadVisiblePages() {
  const { top, bottom } = grid.scrollIndexRect;
  for (let row = top; row <= bottom && row < total; row++) {
    if (!data[row].loaded) loadPage(Math.floor(row / pageSize));
  }
}

let debounce: number | undefined;
grid.addEventListener('scroll', function () {
  window.clearTimeout(debounce);
  debounce = window.setTimeout(loadVisiblePages, 100);
});

app.append(gridElement);
loadVisiblePages();