# Infinite Scrolling

```ts
import { Grid } from 'cx/widgets';
```

Infinite scrolling dynamically loads data as the user scrolls. The grid fetches additional pages of data based on scroll position, providing a seamless experience for large datasets.

```tsx
import { createModel } from "cx/data";
import { KeySelection } from "cx/ui";
import { Grid } from "cx/widgets";

interface Record {
  id: number;
  fullName: string;
  continent: string;
  browser: string;
  visits: number;
}

interface PageModel {
  selection: number;
}

const m = createModel<PageModel>();

const names = [
  "Alice Johnson",
  "Bob Smith",
  "Carol White",
  "David Brown",
  "Eva Green",
];
const continents = [
  "North America",
  "Europe",
  "Asia",
  "South America",
  "Africa",
];
const browsers = ["Chrome", "Firefox", "Safari", "Edge", "Opera"];

export default (
  <Grid
    infinite
    style="height: 400px"
    lockColumnWidths
    cached
    keyField="id"
    selection={{ type: KeySelection, bind: m.selection, keyField: "id" }}
    onFetchRecords={({ page, pageSize }) =>
      new Promise<{ records: Record[]; totalRecordCount: number }>(
        (resolve) => {
          // Simulate server delay
          setTimeout(() => {
            const records: Record[] = [];
            for (let i = 0; i < pageSize; i++) {
              const id = (page - 1) * pageSize + i + 1;
              records.push({
                id,
                fullName: names[id % 5] + " " + id,
                continent: continents[id % 5],
                browser: browsers[id % 5],
                visits: Math.floor(Math.random() * 100) + 1,
              });
            }
            resolve({
              records,
              totalRecordCount: 10000,
            });
          }, 100);
        },
      )
    }
    columns={[
      {
        header: "#",
        field: "id",
        sortable: true,
        defaultWidth: 70,
        align: "center",
      },
      { header: "Name", field: "fullName", sortable: true },
      {
        header: "Continent",
        field: "continent",
        sortable: true,
        defaultWidth: 130,
      },
      {
        header: "Browser",
        field: "browser",
        sortable: true,
        defaultWidth: 100,
      },
      {
        header: "Visits",
        field: "visits",
        sortable: true,
        align: "right",
        defaultWidth: 70,
      },
    ]}
  />
);

```

Scroll down to load more records. This grid can display up to 10,000 records, loaded on demand.

## How It Works

Enable infinite scrolling by setting the `infinite` flag and implementing the `onFetchRecords` callback:

```tsx
<Grid
  infinite
  style="height: 400px"
  onFetchRecords={({ page, pageSize, sorters }) =>
    fetchFromServer(page, pageSize, sorters).then((data) => ({
      records: data.items,
      totalRecordCount: data.total,
    }))
  }
  columns={columns}
/>
```

The `onFetchRecords` callback receives fetch parameters and should return a Promise that resolves to an object with the fetched records and pagination info.

## Fetch Parameters

The `onFetchRecords` callback receives an object with:

- `page` - Current page number (1-indexed)
- `pageSize` - Number of records per page
- `sorters` - Array of active sorters `[{ field, direction }]`
- `state` - Custom state passed from previous fetch result

## Fetch Result

The callback should return a Promise resolving to:

| Property | Type | Description |
| -------- | ---- | ----------- |
| `records` | `array` | Array of records for the current page. |
| `totalRecordCount` | `number` | Total number of records available. Used to calculate scroll height. |
| `lastPage` | `boolean` | Alternative to `totalRecordCount`. Set to `true` when there are no more records. |
| `state` | `any` | Custom state (e.g., cursor or token) passed to the next fetch call. |

## Configuration

| Property | Type | Description |
| -------- | ---- | ----------- |
| `infinite` | `boolean` | Enables infinite scrolling mode. |
| `onFetchRecords` | `function` | Callback that fetches records. Receives fetch parameters and returns a Promise. |
| `pageSize` | `number` | Number of records to fetch per page. Default is `100`. |
| `lockColumnWidths` | `boolean` | Locks column widths to prevent layout shifts during data loads. |
| `lockColumnWidthsRequiredRowCount` | `number` | Minimum number of rows required before column widths are locked. |
| `cached` | `boolean` | Prevents row re-renders when unrelated store data changes. |

See also: [Buffering](/docs/tables/buffering), [Pagination](/docs/tables/pagination)