React DevExtremee - Rows are labeled incorrectly in the DataGrid
05:04 13 May 2026

I've added listeners to my app that are supposed to select a row and, when I press Shift + Up or Down Arrow, select the next rows. It works fine until I reach the bottom or top edge, at which point the focus is lost.

I can’t load everything at once because we have about 8,000 records in the DataGrid in production; I have to load them using `scrolling.mode: virtual`.

How can this be handled in DevExtreme?

import React, { useRef, useCallback, useEffect } from 'react';
import DataGrid, {
  Scrolling, Paging, Column, HeaderFilter, Search, Selection,
} from 'devextreme-react/data-grid';
import * as AspNetData from 'devextreme-aspnet-data-nojquery';

const dataSource = AspNetData.createStore({
  key: 'Id',
  loadUrl: 'https://js.devexpress.com/Demos/WidgetsGalleryDataService/api/Sales',
});

const App = () => {
  // Instance gridu - uložená přes onInitialized
  const gridInstanceRef = useRef(null);
  const wrapperRef = useRef(null);
  const anchorIndexRef = useRef(null);
  const activeIndexRef = useRef(null);

  const handleInitialized = useCallback((e: any) => {
    gridInstanceRef.current = e.component;
    console.log('[Init] grid instance saved:', !!e.component);
    console.log('[Init] has selectRows:', typeof e.component?.selectRows);
    console.log('[Init] has getSelectedRowKeys:', typeof e.component?.getSelectedRowKeys);
  }, []);

  const handleRowClick = useCallback((e: any) => {
    const grid = gridInstanceRef.current;
    if (!grid) {
      console.log('[RowClick] no grid');
      return;
    }
    if (e.rowType !== 'data') return;

    const rowIndex = e.rowIndex;
    const nativeEvent = e.event;

    console.log('[RowClick] rowIndex:', rowIndex, 'shift:', nativeEvent?.shiftKey);

    if (nativeEvent?.shiftKey && anchorIndexRef.current !== null) {
      const anchor = anchorIndexRef.current;
      const start = Math.min(anchor, rowIndex);
      const end = Math.max(anchor, rowIndex);
      const keys: any[] = [];
      for (let i = start; i <= end; i++) {
        const key = grid.getKeyByRowIndex(i);
        if (key !== undefined) keys.push(key);
      }
      grid.selectRows(keys, false);
      activeIndexRef.current = rowIndex;
    } else {
      // selectRowsByIndexes nemusí existovat - použijeme getKeyByRowIndex + selectRows
      const key = grid.getKeyByRowIndex(rowIndex);
      if (key !== undefined) {
        grid.selectRows([key], false);
      }
      anchorIndexRef.current = rowIndex;
      activeIndexRef.current = rowIndex;
    }
  }, []);

  useEffect(() => {
    const wrapper = wrapperRef.current;
    if (!wrapper) return;

    const onKeyDown = (event: KeyboardEvent) => {
      if (!event.shiftKey) return;
      if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return;

      const grid = gridInstanceRef.current;
      if (!grid) {
        console.log('[KeyDown] no grid instance');
        return;
      }

      const target = event.target as HTMLElement;
      if (!wrapper.contains(target)) return;
      const tag = target?.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA' || target?.isContentEditable) return;

      // Zastav DŘÍV než DX naviguje
      event.preventDefault();
      event.stopPropagation();
      event.stopImmediatePropagation();

      // Resolve anchor
      if (anchorIndexRef.current === null) {
        const selected = grid.getSelectedRowKeys();
        console.log('[KeyDown] anchor null, selected:', selected);
        if (selected && selected.length > 0) {
          const idx = grid.getRowIndexByKey(selected[0]);
          if (idx >= 0) {
            anchorIndexRef.current = idx;
            activeIndexRef.current = idx;
          }
        } else {
          const focused = grid.option('focusedRowIndex');
          if (typeof focused === 'number' && focused >= 0) {
            anchorIndexRef.current = focused;
            activeIndexRef.current = focused;
          } else {
            console.log('[KeyDown] no anchor available');
            return;
          }
        }
      }

      let active = activeIndexRef.current ?? anchorIndexRef.current!;
      active = event.key === 'ArrowDown' ? active + 1 : active - 1;

      const totalCount = grid.totalCount();
      if (active < 0) active = 0;
      if (totalCount >= 0 && active >= totalCount) active = totalCount - 1;

      activeIndexRef.current = active;

      const anchor = anchorIndexRef.current!;
      const start = Math.min(anchor, active);
      const end = Math.max(anchor, active);

      console.log('[KeyDown] anchor:', anchor, 'active:', active, 'range:', start, '-', end);

      const keys: any[] = [];
      for (let i = start; i <= end; i++) {
        const k = grid.getKeyByRowIndex(i);
        if (k !== undefined) keys.push(k);
      }

      console.log('[KeyDown] keys to select:', keys.length);

      if (keys.length > 0) {
        grid.selectRows(keys, false);
        const cell = grid.getCellElement(active, 0);
        if (cell) {
          grid.focus(cell);
        }
      }
    };

    document.addEventListener('keydown', onKeyDown, true);
    return () => {
      document.removeEventListener('keydown', onKeyDown, true);
    };
  }, []);

  return (
    
); }; export default App;
reactjs datagrid devextreme