Infinite scroll in React Grid component

7 Mar 202424 minutes to read

The infinite scrolling feature in the Grid is a powerful tool for seamlessly handling extensive data sets without compromising grid performance. It operates on a “load-on-demand” concept, ensuring that data is fetched only when needed. In the default infinite scrolling mode, a new block of data is loaded each time the scrollbar reaches the end of the vertical scroller. This approach significantly enhances the user experience when working with large data collections in the React Grid.

In this mode, a block of data accumulates every time the scrollbar reaches the end of the scroller. To clarify, in this context, a block represents the pageSize of the Grid. If the pageSize is not explicitly specified, the Grid will automatically calculate it based on the grid viewport height and row height.

To enable infinite scrolling, you need to define enableInfiniteScrolling as true and content height by height property.

In this feature, the Grid will not initiate a new data request when revisiting the same page.
The height property must be specified when enabling enableInfiniteScrolling.

The following an example that demonstrates how to enable infinite scroll in the Grid:

import { ColumnDirective, ColumnsDirective, GridComponent, Inject } from '@syncfusion/ej2-react-grids';
import { InfiniteScroll } from '@syncfusion/ej2-react-grids';
import * as React from 'react';
import { data } from './largeData';
function App() {
    const datas = data(5000);
    const pageSettings = { pageSize: 50 };
    return (<GridComponent dataSource={datas} height={300} enableInfiniteScrolling={true} pageSettings={pageSettings}>
        <Inject services={[InfiniteScroll]} />
        <ColumnsDirective>
            <ColumnDirective field='TaskID' headerText='Task ID' width='70' textAlign='Right' />
            <ColumnDirective field='Engineer' width='100' />
            <ColumnDirective field='Designation' width='100' />
            <ColumnDirective field='Estimation' headerText='Estimation' textAlign='Right' width='100' />
            <ColumnDirective field='Status' width='100' />
        </ColumnsDirective>
    </GridComponent>);
}
export default App;
import { ColumnDirective, ColumnsDirective, GridComponent, Inject } from '@syncfusion/ej2-react-grids';
import { PageSettingsModel, InfiniteScroll } from '@syncfusion/ej2-react-grids';
import * as React from 'react';
import { data } from './largeData';

function App() {
  const datas: object[] = data(5000);
  const pageSettings: PageSettingsModel = { pageSize: 50 };
  return (<GridComponent dataSource={datas} height={300} enableInfiniteScrolling={true} pageSettings={pageSettings}>
    <Inject services={[InfiniteScroll]} />
    <ColumnsDirective>
      <ColumnDirective field='TaskID' headerText='Task ID' width='70' textAlign='Right' />
      <ColumnDirective field='Engineer' width='100' />
      <ColumnDirective field='Designation' width='100' />
      <ColumnDirective field='Estimation' headerText='Estimation' textAlign='Right' width='100' />
      <ColumnDirective field='Status' width='100' />
    </ColumnsDirective>
  </GridComponent>)
}
export default App;
const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross',
    'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green',
    'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing'];
export function data(count) {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
            Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
            Engineer: names[Math.round(Math.random() * names.length)] || names[0],
            Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
            Status: status[Math.round(Math.random() * status.length)] || status[0],
            TaskID: i + 1
        });
    }
    return result;
}
;
const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 
              'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green', 
              'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
export function data(count: number) {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
          Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
          Engineer: names[Math.round(Math.random() * names.length)] || names[0],
          Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
          Status: status[Math.round(Math.random() * status.length)] || status[0],
          TaskID: i + 1
        });
    }
    return result;
};

Number of blocks rendered during initial loading

The number of blocks to be initially rendered when the Grid is loaded. Each block corresponds to a page size of the Grid, resulting in the rendering of a certain number of row elements determined by multiplying the initial block size with the page size.

You can define the initial loading pages count by using infiniteScrollSettings.initialBlocks property . By default, this property loads three pages during the initial rendering. Subsequently, additional data is buffered and loaded based on either the page size or the number of rows rendered within the provided height.

The following an example of how you can use the initialBlocks property to set the initial loading pages based on DropDownList input:

import { ColumnDirective, ColumnsDirective, GridComponent, Inject } from '@syncfusion/ej2-react-grids';
import { PageSettingsModel, InfiniteScroll } from '@syncfusion/ej2-react-grids';
import { ChangeEventArgs, DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
import * as React from 'react';
import { data } from './largeData';

function App() {
  let grid;
  const datas = data(1000);
  const pageSettings = { pageSize: 50 };
  const dropDownData = [
    { text: 'Select count' },
    { text: '1', value: '1' },
    { text: '2', value: '2' },
    { text: '3', value: '3' },
    { text: '4', value: '4' },
    { text: '5', value: '5' },
    { text: '6', value: '6' },
    { text: '7', value: '7' }
  ];
  const onChange = (args) => {
    grid.infiniteScrollSettings.initialBlocks = parseInt((args.value ), 10);
    grid.refresh();
  }
  return (<div>
    <label style=>Select initialBlocks count: </label>
    <DropDownListComponent index={0} width={200} dataSource={dropDownData} change={onChange}></DropDownListComponent>
    <GridComponent ref={g => grid = g} dataSource={datas} height={300} enableInfiniteScrolling={true} pageSettings={pageSettings}>
      <Inject services={[InfiniteScroll]} />
      <ColumnsDirective>
        <ColumnDirective field='TaskID' headerText='Task ID' width='70' textAlign='Right' />
        <ColumnDirective field='Engineer' width='100' />
        <ColumnDirective field='Designation' width='100' />
        <ColumnDirective field='Estimation' headerText='Estimation' textAlign='Right' width='100' />
        <ColumnDirective field='Status' width='100' />
      </ColumnsDirective>
    </GridComponent></div>)
}
export default App;
import { ColumnDirective, ColumnsDirective, GridComponent, Inject } from '@syncfusion/ej2-react-grids';
import { PageSettingsModel, InfiniteScroll } from '@syncfusion/ej2-react-grids';
import { ChangeEventArgs, DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
import * as React from 'react';
import { data } from './largeData';

function App() {
  let grid: GridComponent | null;
  const datas: object[] = data(1000);
  const pageSettings: PageSettingsModel = { pageSize: 50 };
  const dropDownData: { [key: string]: Object; }[] = [
    { text: 'Select count' },
    { text: '1', value: '1' },
    { text: '2', value: '2' },
    { text: '3', value: '3' },
    { text: '4', value: '4' },
    { text: '5', value: '5' },
    { text: '6', value: '6' },
    { text: '7', value: '7' }
  ];
  const onChange = (args: ChangeEventArgs) => {
    (grid as GridComponent).infiniteScrollSettings.initialBlocks = parseInt((args.value as string), 10);
    (grid as GridComponent).refresh();
  }
  return (<div>
    <label style=>Select initialBlocks count: </label>
    <DropDownListComponent index={0} width={200} dataSource={dropDownData} change={onChange}></DropDownListComponent>
    <GridComponent ref={g => grid = g} dataSource={datas} height={300} enableInfiniteScrolling={true} pageSettings={pageSettings}>
      <Inject services={[InfiniteScroll]} />
      <ColumnsDirective>
        <ColumnDirective field='TaskID' headerText='Task ID' width='70' textAlign='Right' />
        <ColumnDirective field='Engineer' width='100' />
        <ColumnDirective field='Designation' width='100' />
        <ColumnDirective field='Estimation' headerText='Estimation' textAlign='Right' width='100' />
        <ColumnDirective field='Status' width='100' />
      </ColumnsDirective>
    </GridComponent></div>)
}
export default App;
const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross',
    'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green',
    'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing'];
export function data(count) {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
            Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
            Engineer: names[Math.round(Math.random() * names.length)] || names[0],
            Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
            Status: status[Math.round(Math.random() * status.length)] || status[0],
            TaskID: i + 1
        });
    }
    return result;
}
;
const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 
              'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green', 
              'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
export function data(count: number) {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
          Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
          Engineer: names[Math.round(Math.random() * names.length)] || names[0],
          Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
          Status: status[Math.round(Math.random() * status.length)] || status[0],
          TaskID: i + 1
        });
    }
    return result;
};

Efficient data caching and DOM management in grid cache mode

In Grid cache mode, cached data blocks are reused when revisiting them, reducing the need for frequent data requests while navigating the same block. This mode also manages DOM row elements based on the infiniteScrollSettings.maxBlocks count value. If this limit is exceeded, it removes a block of row elements to create new rows.

To enable cache mode, you need to define enableCache property of infiniteScrollSettings as true.

To enable maximum blocks, you need to define maxBlocks count of infiniteScrollSettings, By default this property value is 3.

The following example that demonstrates how to enable/disable cache mode in infinite scrolling of the grid based on a Switch componentchange event :

import { ColumnDirective, ColumnsDirective, GridComponent, Inject } from '@syncfusion/ej2-react-grids';
import { PageSettingsModel, InfiniteScroll } from '@syncfusion/ej2-react-grids';
import { SwitchComponent, ChangeEventArgs } from '@syncfusion/ej2-react-buttons';
import * as React from 'react';
import { data } from './largeData';

function App() {
    let grid;
    const datas = data(1000);
    const pageSettings = { pageSize: 50 };
    const onChange = (args) => {
        grid.infiniteScrollSettings.enableCache = args.checked;
        grid.refresh();
    }
    return (<div>
        <label>Enable/Disable Cache mode: </label>
        <SwitchComponent change={onChange}></SwitchComponent>
        <GridComponent ref={g => grid = g} dataSource={datas} height={300} enableInfiniteScrolling={true} pageSettings={pageSettings}>
            <Inject services={[InfiniteScroll]} />
            <ColumnsDirective>
                <ColumnDirective field='TaskID' headerText='Task ID' width='70' textAlign='Right' />
                <ColumnDirective field='Engineer' width='100' />
                <ColumnDirective field='Designation' width='100' />
                <ColumnDirective field='Estimation' headerText='Estimation' textAlign='Right' width='100' />
                <ColumnDirective field='Status' width='100' />
            </ColumnsDirective>
        </GridComponent></div>)
}
export default App;
import { ColumnDirective, ColumnsDirective, GridComponent, Inject } from '@syncfusion/ej2-react-grids';
import { PageSettingsModel, InfiniteScroll } from '@syncfusion/ej2-react-grids';
import { SwitchComponent, ChangeEventArgs } from '@syncfusion/ej2-react-buttons';
import * as React from 'react';
import { data } from './largeData';

function App() {
  let grid: GridComponent | null;
  const datas: object[] = data(1000);
  const pageSettings: PageSettingsModel = { pageSize: 50 };
  const onChange = (args: ChangeEventArgs) => {
    (grid as GridComponent).infiniteScrollSettings.enableCache = args.checked;
    (grid as GridComponent).refresh();
  }
  return (<div>
    <label>Enable/Disable Cache mode: </label>
    <SwitchComponent change={onChange}></SwitchComponent>
    <GridComponent ref={g => grid = g} dataSource={datas} height={300} enableInfiniteScrolling={true} pageSettings={pageSettings}>
      <Inject services={[InfiniteScroll]} />
      <ColumnsDirective>
        <ColumnDirective field='TaskID' headerText='Task ID' width='70' textAlign='Right' />
        <ColumnDirective field='Engineer' width='100' />
        <ColumnDirective field='Designation' width='100' />
        <ColumnDirective field='Estimation' headerText='Estimation' textAlign='Right' width='100' />
        <ColumnDirective field='Status' width='100' />
      </ColumnsDirective>
    </GridComponent></div>)
}
export default App;
const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross',
    'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green',
    'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing'];
export function data(count) {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
            Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
            Engineer: names[Math.round(Math.random() * names.length)] || names[0],
            Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
            Status: status[Math.round(Math.random() * status.length)] || status[0],
            TaskID: i + 1
        });
    }
    return result;
}
;
const names = ['TOM', 'Hawk', 'Jon', 'Chandler', 'Monica', 'Rachel', 'Phoebe', 'Gunther', 'Ross', 
              'Geller', 'Joey', 'Bing', 'Tribbiani', 'Janice', 'Bong', 'Perk', 'Green', 
              'Ken', 'Adams'];
const hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const designation = ['Manager', 'Engineer 1', 'Engineer 2', 'Developer', 'Tester'];
const status = ['Completed', 'Open', 'In Progress', 'Review', 'Testing']
export function data(count: number) {
    const result = [];
    for (let i = 0; i < count; i++) {
        result.push({
          Designation: designation[Math.round(Math.random() * designation.length)] || designation[0],
          Engineer: names[Math.round(Math.random() * names.length)] || names[0],
          Estimation: hours[Math.round(Math.random() * hours.length)] || hours[0],
          Status: status[Math.round(Math.random() * status.length)] || status[0],
          TaskID: i + 1
        });
    }
    return result;
};

Limitations

  • Due to the element height limitation in browsers, the maximum number of records loaded by the grid is limited due to the browser capability.
  • It is necessary to set a static height for the component or its parent container when using infinite scrolling. The 100% height will work only if the component height is set to 100%, and its parent container has a static height.
  • When infinite scrolling is activated, compatibility for copy-paste and drag-and-drop operations is limited to the data items visible in the current viewport of the grid.
  • Cell selection will not be persisted in cache mode.
  • The group records cannot be collapsed in cache mode.
  • Lazy load grouping with infinite scrolling does not support cache mode, and the infinite scrolling mode is exclusively applicable to parent-level caption rows in this scenario.
  • The aggregated information and total group items are displayed based on the current view items. To get these information regardless of the view items, refer to the Group with paging topic.
  • Programmatic selection using the selectRows and selectRow method is not supported in infinite scrolling.
  • Infinite scrolling is not compatible with the following features:
    1. Batch editing
    2. Normal editing
    3. Row spanning
    4. Column spanning
    5. Row template
    6. Row virtual scrolling
    7. Detail template
    8. Hierarchy features
    9. Autofill
  • Limitations of row drag and drop with infinite scrolling
    1. In cache mode, the grid refreshes automatically if the content’s tr element count exceeds the cache limit of the grid’s content after the drop action.
    2. When performing row drag and drop with lazy load grouping, the grid will refresh automatically.
    3. In remote data, changes are applied only in the UI. They will be lost once the grid is refreshed. To restore them, you need to update the changes in your database. By using the rowDrop event, you can send the request to the server and apply the changes in your database. After this, you need to refresh the grid to show the updated data.