# Lazy loading/Infinite scrolling

CanvasGrid invokes `onViewPortChange` callback whenever user scrolls or pans around the spreadsheet.

This can be used to load data for the next set of visible rows.

{% hint style="info" %}
Do note that lazy loading can affect calculations if cell dependents are out of the viewport and not loaded initially.

We recommend lazy loading only if calculations are moved to the server side.
{% endhint %}

{% code overflow="wrap" %}

```tsx
import { CanvasGrid } from "@rowsncolumns/spreadsheet"

const App = () => {
  const [ sheetData, onChangeSheetData ] = useState<SheetData<T>>({})
  <CanvasGrid
    onViewPortChange={(viewport: ViewPortProps) => {
      const {
        columnStartIndex, 
        columnStopIndex,
        rowStartIndex,
        rowStopIndex,
        visibleColumnStartIndex,
        visibleColumnStopIndex,
        visibleRowStartIndex,
        visibleRowStopIndex      
      } = viewport
      
      // Throttle fetch request
      // Move it out of the render loop, this is just an example
      throttle(
        fetch(`/rows?start=${rowStartIndex}&end=${rowStopIndex}`)
          .then(rowData => {
            // Append new row data
            onChangeSheetData(prev => {
              const newRowData = prev[sheetId]
                  .splice(rowStartIndex, rowStopIndex - rowStartIndex, ...rowData)
              return {
                ...prev,
                [sheetId]: newRowData
              }
            })
          })
      , 300)
    }}
  />
}
```

{% endcode %}

## Hooks for Lazy loading

`@rowsncolumns/spreadsheet-state` exports the `useAsyncDatasource` hook to load paged data for the current viewport while caching nearby pages.

### Usage

```tsx
import { CanvasGrid, CellData, SpreadsheetProvider } from "@rowsncolumns/spreadsheet";
import { RowData, SheetData, useAsyncDatasource } from "@rowsncolumns/spreadsheet-state";
import { CircularLoader } from "@rowsncolumns/ui";

const App = () => {
  const locale = "en-US";
  const [sheetData, onChangeSheetData] = useState<SheetData<CellData>>({});
  const pageSize = 100;
  const rowCount = 100_000;
  const columnCount = 100;
  const sheetId = 1;

  const buildRow = (rowIndex: number): RowData<CellData | null> => {
    const values: (CellData | null)[] = Array.from(
      { length: columnCount + 1 },
      () => null
    );
    values[1] = {
      ue: { sv: `Row ${rowIndex}` },
      fv: `Row ${rowIndex}`,
    };
    values[2] = {
      ue: { nv: rowIndex },
      fv: String(rowIndex),
    };
    values[3] = {
      ue: { sv: `Page ${Math.floor(rowIndex / pageSize)}` },
      fv: `Page ${Math.floor(rowIndex / pageSize)}`,
    };
    return { values };
  };

  const getRowData = useCallback(
    async (_sheetId: number, [rowStartIndex, rowStopIndex]: [number, number]) => {
      await new Promise((res) => setTimeout(res, 200));
      const rows: RowData<CellData | null>[] = [];
      for (let rowIndex = rowStartIndex; rowIndex < rowStopIndex; rowIndex++) {
        rows.push(buildRow(rowIndex));
      }
      return rows;
    },
    [pageSize]
  );

  const { onViewPortChange, isLoading } = useAsyncDatasource<CellData>({
    sheetId,
    locale,
    pageSize,
    bufferPages: 2,
    maxCachedPages: 6,
    rowCount,
    getRowData,
    onChangeSheetData,
  });
  
  return (
    <SpreadsheetProvider>
      <div className="relative flex min-h-[70vh] flex-1">
        <CanvasGrid<CellData>
          sheetId={sheetId}
          rowCount={rowCount}
          columnCount={columnCount}
          onViewPortChange={onViewPortChange}
          getCellData={(targetSheetId, rowIndex, columnIndex) =>
            sheetData?.[targetSheetId]?.[rowIndex]?.values?.[columnIndex]
          }
          readonly
          licenseKey="evaluation-license"
        />
        {isLoading ? (
          <CircularLoader className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10" />
        ) : null}
      </div>
    </SpreadsheetProvider>
  );
}
```

Notes:

* `bufferPages` keeps a cushion of pages around the viewport.
* `maxCachedPages` caps total cached pages and evicts the farthest ones.
* `rowCount` prevents over-fetching beyond your backend total.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rowsncolumns.app/configuration/features/lazy-loading-infinite-scrolling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
