# grid-virtual-scrolling

> Data grid virtualizes large order data and supports seamless scrolling and cell editing.

**Framework:** react  **Component:** Grid  **Variant:** virtual-scrolling

## Get this item

**If you are an agent, fetch the JSON.** Source is inlined, so one request is enough and no tooling is required:

```
GET https://ai.syncfusion.com/r/react/ts/grid-virtual-scrolling.json
```

**Install Package(s)**

```bash
npm install @syncfusion/ej2-react-grids @syncfusion/ej2-react-buttons @syncfusion/ej2-react-inputs
```

**Notes**

- Syncfusion release: 2026 Volume 2 (v34.1.29)
- The Syncfusion package is licensed. The composition in this file is source you own and edit. See https://ai.syncfusion.com/licensing.md

## Source files

### src/components/grid-virtual-scrolling/Grid.tsx

```tsx
import { GridComponent, ColumnsDirective, ColumnDirective, Inject, VirtualScroll, Edit, Toolbar, type LoadEventArgs } from '@syncfusion/ej2-react-grids';
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
import { RatingComponent } from '@syncfusion/ej2-react-inputs';

let virtualOrderData = []; // or define proper interface if needed
function createVirtualOrderData() {
    function pmRandom(seed) {
        let t = (seed % 2147483647);
        if (t <= 0)
            t += 2147483646;
        return function () {
            t = (t * 16807) % 2147483647;
            return (t - 1) / 2147483646;
        };
    }
    const seed = 123456789;
    const rand = pmRandom(seed);
    function randInt(min, max) {
        return Math.floor(rand() * (max - min + 1)) + min;
    }
    function randChoice(arr) {
        return arr[Math.floor(rand() * arr.length)];
    }
    function pickAvoidTriplet(arr, fieldName) {
        let v = randChoice(arr);
        if (virtualOrderData.length >= 2) {
            const prevItems = virtualOrderData;
            const a = prevItems[virtualOrderData.length - 1][fieldName];
            const b = prevItems[virtualOrderData.length - 2][fieldName];
            if (a === b && a === v) {
                const alt = arr.filter(s => s !== v);
                if (alt.length > 0) {
                    v = randChoice(alt);
                }
            }
        }
        return v;
    }
    const names = [
        'Maria', 'Ana Trujillo', 'Antonio Moreno', 'Thomas Hardy', 'Christina Berglund',
        'Hanna Moos', 'Frederique Citeaux', 'Martin Sommer', 'Laurence Lebihan', 'Elizabeth Lincoln',
        'Victoria Ashworth', 'Patricio Simpson', 'Francisco Chang', 'Yang Wang', 'Pedro Afonso',
        'Elizabeth Brown', 'Sven Ottlieb', 'Janine Labrune', 'Ann Devon', 'Roland Mendel',
        'Aria Cruz', 'Diego Roel', 'Martine Rance', 'Maria Larsson', 'Peter Franken',
        'Carine Schmitt', 'Paolo Accorti', 'Lino Rodriguez', 'Eduardo Saavedra', 'Jose Pedro Freyre',
        'Andre Fonseca', 'Howard Snyder', 'Manuel Pereira', 'Mario Pontes', 'Carlos Hernández',
        'Yoshi Latimer', 'Patricia McKenna', 'Helen Bennett', 'Philip Cramer', 'Daniel Tonini',
        'Annette Roulet', 'Yoshi Tannamuri', 'John Steel', 'Renate Messner', 'Jaime Yorres',
        'Carlos Gonzalez', 'Felipe Izquierdo', 'Fran Wilson', 'Giovanni Rovelli', 'Catherine Dewey',
        'Jean Fresnière', 'Alexander Feuer', 'Simon Crowther', 'Yvonne Moncada', 'Rene Phillips',
        'Henriette Pfalzheim', 'Marie Bertrand', 'Guillermo Fernandez', 'Georg Pipps', 'Isabel de Castro',
        'Bernardo Batista', 'Lucia Carvalho', 'Horst Kloss', 'Sergio Gutierrez', 'Paula Wilson',
        'Maurizio Moroni', 'Janete Limeira', 'Michael Holz', 'Alejandra Camino', 'Jonas Bergulfsen',
        'Jose Pavarotti', 'Hari Kumar', 'Jytte Petersen', 'Dominique Perrier', 'Art Braunschweiger',
        'Pascale Cartrain', 'Liz Nixon', 'Liu Wong', 'Karin Josephs', 'Miguel Angel Paolino',
        'Anabela Domingues', 'Helvetius Nagy', 'Palle Ibsen', 'Mary Saveley', 'Paul Henriot',
        'Rita Muller', 'Pirkko Koskitalo', 'Paula Parente', 'Karl Jablonski', 'Matti Karttunen',
        'Zbyszek Piestrzeniewicz'
    ];
    const products = [
        'Chai', 'Chang', 'Aniseed Syrup', "Chef Anton's Cajun Seasoning",
        "Grandma's Boysenberry Spread", "Uncle Bob's Organic Dried Pears",
        'Mishi Kobe Niku', 'Ikura', 'Queso Cabrales', 'Pavlova'
    ];
    const categories = [
        'Beverages', 'Condiments', 'Confections', 'Dairy Products',
        'Grains/Cereals', 'Meat/Poultry', 'Seafood'
    ];
    const paymentMethods = ['Card', 'Digital', 'Cash'];
    const orderStatuses = ['Ordered', 'Processing', 'Packed', 'Shipped', 'Delivered', 'Canceled', 'Returned'];
    const priorities = ['Low', 'Medium', 'High', 'Critical'];
    const cities = ['Seattle', 'Austin', 'Boston', 'Chicago', 'San Francisco', 'New York'];
    const states = ['WA', 'TX', 'MA', 'IL', 'CA', 'NY'];
    const countries = ['USA', 'Canada', 'Mexico', 'UK', 'Germany', 'France'];
    const warehouses = ['WH-A', 'WH-B', 'WH-C', 'WH-D'];
    const productToCategory = {};
    for (let k = 0; k < products.length; k++) {
        productToCategory[products[k]] = categories[k % categories.length];
    }
    function computeAmounts(qty, unitPrice, discountPct, taxPct, shippingFee) {
        const gross = qty * unitPrice;
        const discount = gross * (discountPct / 100);
        const subtotal = gross - discount;
        const tax = subtotal * (taxPct / 100);
        const total = subtotal + tax + shippingFee;
        return {
            subtotal: Math.round(subtotal * 100) / 100,
            taxAmount: Math.round(tax * 100) / 100,
            totalAmount: Math.round(total * 100) / 100
        };
    }
    const baseTime = new Date(2025, 0, 1).getTime();
    for (let i = 1; i <= 100000; i++) {
        const qty = randInt(1, 10);
        const unitPrice = Math.round((rand() * 500 + 5) * 100) / 100;
        const discountPct = randInt(0, 20);
        const taxPct = randInt(0, 18);
        const shippingFee = Math.round((rand() * 20) * 100) / 100;
        const amounts = computeAmounts(qty, unitPrice, discountPct, taxPct, shippingFee);
        const orderDate = new Date(baseTime - randInt(0, 90) * 24 * 3600 * 1000);
        const shippedDate = new Date(orderDate.getTime() + randInt(1, 14) * 24 * 3600 * 1000);
        const customerId = randInt(1000, 9999);
        const custName = randChoice(names);
        const email = custName.toLowerCase().replace(/\s+/g, '.') + '@example.com';
        const paymentMethod = pickAvoidTriplet(paymentMethods, 'PaymentMethod');
        const rating = randInt(1, 5);
        const orderStatusVal = pickAvoidTriplet(orderStatuses, 'OrderStatus');
        const priorityVal = pickAvoidTriplet(priorities, 'Priority');
        const productName = randChoice(products);
        const derivedCategory = productToCategory[productName] || randChoice(categories);
        const warehouse = randChoice(warehouses);
        const inventoryCount = randInt(0, 500);
        let paymentStatus = 'Pending';
        if (orderStatusVal === 'Delivered' || orderStatusVal === 'Shipped') {
            paymentStatus = 'Paid';
        }
        else if (orderStatusVal === 'Canceled' || orderStatusVal === 'Returned') {
            if (rating <= 1) {
                paymentStatus = 'Failed';
            }
            else {
                paymentStatus = (paymentMethod === 'Cash') ? 'Pending' : 'Refunded'; // assuming 'Cash' ≈ COD
            }
        }
        else if (orderStatusVal === 'Packed') {
            paymentStatus = (paymentMethod === 'Cash') ? 'Pending' : 'Paid';
        }
        else if (orderStatusVal === 'Ordered' || orderStatusVal === 'Processing') {
            paymentStatus = 'Pending';
        }
        virtualOrderData.push({
            OrderID: `ORD-${1000 + i}`,
            OrderDate: orderDate,
            ShipDate: shippedDate,
            CustomerID: `CUS-${customerId}`,
            CustomerName: custName,
            Email: email,
            Phone: `+1-${randInt(200, 999)}-${randInt(1000, 9999)}`,
            ShipAddress: `${randInt(10, 999)} ${randChoice(['Main St', 'Market St', '1st Ave', 'Broadway'])}`,
            ShipCity: randChoice(cities),
            ShipState: randChoice(states),
            ShipPostalCode: String(randInt(10000, 99999)),
            ShipCountry: randChoice(countries),
            ProductID: `PROD-${randInt(10000, 99999)}`,
            ProductName: productName,
            Category: derivedCategory,
            Quantity: qty,
            UnitPrice: unitPrice,
            Discount: discountPct,
            Tax: taxPct,
            SubTotal: amounts.subtotal,
            TaxAmount: amounts.taxAmount,
            ShipFee: shippingFee,
            TotalAmount: amounts.totalAmount,
            PaymentMethod: paymentMethod,
            PaymentStatus: paymentStatus,
            Warehouse: warehouse,
            InventoryCount: inventoryCount,
            Priority: priorityVal,
            OrderStatus: orderStatusVal,
            Rating: rating,
        });
    }
}

// custom code end
function Virtualization() {
    // custom code start
    let grid: GridComponent;
    let date1: number;
    let date2: number;
    let flag: boolean = true;
    let enableVirtualization: boolean = true;
    let data: Object[] = [];
    const toolbarOptions: any = ['Add', 'Edit', 'Delete', 'Update', 'Cancel'];
    const editSettings: any = { allowEditing: true, allowDeleting: true, newRowPosition: 'Top' };
    const validationSno: Object = { required: true, digits: true };
    const validationRule: Object = { required: true };

    function ratingTemplate(props: any) {
        return (<div><RatingComponent value={props.Rating} cssClass={'custom-rating'} readOnly={true} /></div>);
    }
    function paymentMethodTemplate(props: any) {
        return (
            <div className="e-payment-info">
                <img
                    src={`src/grid/images/payment/${props.PaymentMethod}.svg`}
                    alt={props.PaymentMethod}
                />
                <span>{props.PaymentMethod}</span>
            </div>
        );
    };
    function orderStatusTemplate(props: any) {
        if (props.OrderStatus === "Delivered") {
            return (
                <div className="virtual-statustemp e-deliveredcolor">
                    <span className="virtual-statustxt e-deliveredcolor">Delivered</span>
                </div>
            );
        }
        if (props.OrderStatus === "Shipped") {
            return (
                <div className="virtual-statustemp e-shippedcolor">
                    <span className="virtual-statustxt e-shippedcolor">Shipped</span>
                </div>
            );
        }
        if (props.OrderStatus === "Packed") {
            return (
                <div className="virtual-statustemp e-packedcolor">
                    <span className="virtual-statustxt e-packedcolor">Packed</span>
                </div>
            );
        }
        if (props.OrderStatus === "Processing") {
            return (
                <div className="virtual-statustemp e-processingcolor">
                    <span className="virtual-statustxt e-processingcolor">Processing</span>
                </div>
            );
        }
        if (props.OrderStatus === "Canceled") {
            return (
                <div className="virtual-statustemp e-cancelcolor">
                    <span className="virtual-statustxt e-cancelcolor">Canceled</span>
                </div>
            );
        }
        if (props.OrderStatus === "Returned") {
            return (
                <div className="virtual-statustemp e-returnedcolor">
                    <span className="virtual-statustxt e-returnedcolor">Returned</span>
                </div>
            );
        }
        if (props.OrderStatus === "Ordered") {
            return (
                <div className="virtual-statustemp e-orderedcolor">
                    <span className="virtual-statustxt e-orderedcolor">Ordered</span>
                </div>
            );
        }
    };
    function priorityTemplate(props: any) {
        if (props.Priority === "High") {
            return (
                <div className="virtual-statustemp e-highcolor">
                    <span className="virtual-statustxt e-highcolor">High</span>
                </div>
            );
        }
        if (props.Priority === "Low") {
            return (
                <div className="virtual-statustemp e-lowcolor">
                    <span className="virtual-statustxt e-lowcolor">Low</span>
                </div>
            );
        }
        if (props.Priority === "Medium") {
            return (
                <div className="virtual-statustemp e-mediumcolor">
                    <span className="virtual-statustxt e-mediumcolor">Medium</span>
                </div>
            );
        }
        if (props.Priority === "Critical") {
            return (
                <div className="virtual-statustemp e-criticalcolor">
                    <span className="virtual-statustxt e-criticalcolor">Critical</span>
                </div>
            );
        }
    };
    function paymentStatusTemplate(props: any) {
        if (props.PaymentStatus === "Paid") {
            return (
                <div className="virtual-statustemp e-paidcolor">
                    <span className="virtual-statustxt e-paidcolor">Paid</span>
                </div>
            );
        }
        if (props.PaymentStatus === "Pending") {
            return (
                <div className="virtual-statustemp e-pendingcolor">
                    <span className="virtual-statustxt e-pendingcolor">Pending</span>
                </div>
            );
        }
        if (props.PaymentStatus === "Refunded") {
            return (
                <div className="virtual-statustemp e-refundcolor">
                    <span className="virtual-statustxt e-refundcolor">Refunded</span>
                </div>
            );
        }
        if (props.PaymentStatus === "Failed") {
            return (
                <div className="virtual-statustemp e-failedcolor">
                    <span className="virtual-statustxt e-failedcolor">Failed</span>
                </div>
            );
        }
    };

    function onclick() {
        if (!data.length) {
            show();
            createVirtualOrderData();
            date1 = new Date().getTime();
            grid.dataSource = data = virtualOrderData;
            grid.editSettings.allowAdding = true;
        } else {
            flag = true;
            show();
            date1 = new Date().getTime();
            grid.refresh();
        }
    }
    function show() {
        document.getElementById('popup').style.display = 'inline-block';
    }
    function hide() {
        if (flag && date1) {
            date2 = new Date().getTime();
            document.getElementById('performanceTime').innerHTML = 'Time Taken: ' + (date2 - date1) + 'ms';
            flag = false;
        }
        document.getElementById('popup').style.display = 'none';
    }
    function load(args: LoadEventArgs) {
        if (enableVirtualization) {
            args.enableSeamlessScrolling = true;
        }
    }

    return (
        <div className='control-pane'>
            <div className='control-section'>
                <div className='div-button'>
                    <ButtonComponent cssClass={'e-info'} onClick={onclick.bind(this)}>Load 100K Data</ButtonComponent>
                    <span id="popup">
                        <span id="gif" className="imagepop"></span>
                    </span>
                    <span id="performanceTime">Time Taken: 0 ms</span>
                </div>
                <GridComponent id="VirtualScroll" dataSource={[]} enableVirtualization={enableVirtualization} clipMode='EllipsisWithTooltip' enableColumnVirtualization={true} height={400}
                    ref={g => grid = g} dataBound={hide.bind(this)} load={load.bind(this)} toolbar={toolbarOptions} editSettings={editSettings} rowHeight={50}>
                    <ColumnsDirective>
                        <ColumnDirective field="OrderID" headerText="Order ID" width={110} isPrimaryKey={true} validationRules={{ required: true }} />
                        <ColumnDirective field="OrderDate" headerText="Order Date" width={140} format="yMd" textAlign="Right" editType="datepickeredit" />
                        <ColumnDirective field="ShipDate" headerText="Ship Date" width={140} format="yMd" textAlign="Right" editType="datepickeredit" />
                        <ColumnDirective field="OrderStatus" headerText="Order Status" width={140} textAlign="Center" editType="dropdownedit" template={orderStatusTemplate} validationRules={{ required: true }}/>
                        <ColumnDirective field="Priority" headerText="Priority" width={120} textAlign="Center" editType="dropdownedit" template={priorityTemplate} />
                        <ColumnDirective field="CustomerName" headerText="Customer Name" width={190} validationRules={{ required: true }} />
                        <ColumnDirective field="CustomerID" headerText="Customer ID" width={110} visible={false} />
                        <ColumnDirective field="Email" headerText="Email" width={200} />
                        <ColumnDirective field="Phone" headerText="Phone Number" width={140} textAlign="Right" />
                        <ColumnDirective field="ShipAddress" headerText="Ship Address" width={180} />
                        <ColumnDirective field="ShipCity" headerText="Ship City" width={120} />
                        <ColumnDirective field="ShipState" headerText="Ship State Code" width={130} />
                        <ColumnDirective field="ShipPostalCode" headerText="Ship Postal Code" width={130} textAlign="Right" />
                        <ColumnDirective field="ShipCountry" headerText="Ship Country" width={150} />
                        <ColumnDirective field="ProductName" headerText="Product Name" width={250} />
                        <ColumnDirective field="ProductID" headerText="Product ID" width={110} visible={false} />
                        <ColumnDirective field="Category" headerText="Category" width={120} />
                        <ColumnDirective field="Warehouse" headerText="Ware house" width={110} editType="dropdownedit" visible={false}/>
                        <ColumnDirective field="InventoryCount" headerText="Inventory Count" width={150} textAlign="Right" visible={false} />
                        <ColumnDirective field="Quantity" headerText="Quantity" width={100} textAlign="Right" editType="numericedit" edit={{ params: { showSpinButton: false } }} />
                        <ColumnDirective field="UnitPrice" headerText="Unit Price" width={110} format="C2" textAlign="Right" editType="numericedit" edit={{ params: { showSpinButton: false } }} />
                        <ColumnDirective field="Discount" headerText="Discount (%)" width={120} textAlign="Right" editType="numericedit" edit={{ params: { showSpinButton: false } }} />
                        <ColumnDirective field="Tax" headerText="Tax (%)" width={100} textAlign="Right" editType="numericedit" edit={{ params: { showSpinButton: false } }} />
                        <ColumnDirective field="SubTotal" headerText="Sub Total" width={110} format="C2" textAlign="Right" editType="numericedit" edit={{ params: { showSpinButton: false } }} />
                        <ColumnDirective field="TaxAmount" headerText="Tax Amount" width={110} format="C2" textAlign="Right" editType="numericedit" edit={{ params: { showSpinButton: false } }} />
                        <ColumnDirective field="ShipFee" headerText="Ship Fee" width={120} format="C2" textAlign="Right" editType="numericedit" edit={{ params: { showSpinButton: false } }} />
                        <ColumnDirective field="TotalAmount" headerText="Total Amount" width={120} format="C2" textAlign="Right" editType="numericedit" edit={{ params: { showSpinButton: false } }} />
                        <ColumnDirective field="PaymentMethod" headerText="Payment Method" width={140} editType="dropdownedit" template={paymentMethodTemplate} validationRules={{ required: true }}/>
                        <ColumnDirective field="PaymentStatus" headerText="Payment Status" width={140} textAlign="Center" editType="dropdownedit" template={paymentStatusTemplate} validationRules={{ required: true }}/>
                        <ColumnDirective field="Rating" headerText="Delivery Rating" width={160} textAlign="Center" visible={false} template={ratingTemplate} editType="dropdownedit" />
                    </ColumnsDirective>
                    <Inject services={[VirtualScroll, Toolbar, Edit]} />
                </GridComponent>
            </div>
        </div>
    )
}
export default Virtualization;
```
