VirtualTable

A virtualized table component for efficiently rendering large datasets. This component uses tanstack virtual API to render only the visible rows, improving performance.

Available from eds-core/1.20.0

Quick Start

Installation
npm install @adaptavant/eds-core
Import
import { VirtualTable } from '@adaptavant/eds-core';

Basic Usage

The core functionality and features of <VirtualTable /> are the same as the <Table /> component. All requirements outlined in the Table documentation are applicable here as well.

In a virtualized table, rows are rendered dynamically as you scroll, and not all at once. To ensure proper alignment between the header and body rows, each column must have a fixed width. That’s why the size property in the column definition is mandatory — it guarantees consistent layout and alignment across virtualized rows and the static header.

Note: Pagination is not available in <VirtualTable /> as it's designed to be virtually scrollable, making pagination unnecessary.

const [data, setData] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const basicColumnDef = [
  {
    header: "FirstName",
    accessorKey: "name.first",
    size: 150,
  },
  {
    header: "LastName",
    accessorKey: "name.last",
    size: 150,
  },
  {
    header: "Age",
    accessorKey: "dob.age",
    size: 80,
  },
  {
    header: "Phone",
    accessorKey: "phone",
    size: 200,
  },
  {
    header: "Country",
    accessorKey: "location.country",
    size: 150,
  },
];
React.useEffect(() => {
  fetch("https://randomuser.me/api/?results=200")
    .then((response) => response.json())
    .then((data) => {
      setData(data.results);
      setLoading(false);
    })
    .catch((error) => {
      console.log(error);
      setLoading(false);
    });
}, []);
const columns = React.useMemo(() => basicColumnDef, []);
if (loading) {
  return (
    <Box className="flex justify-center w-full p-4 h-[200px]">
      <Box className="flex flex-col items-center justify-center">
        <Loading size="48" />
      </Box>
    </Box>
  );
}
return (
  <Box className="flex align-center justify-center">
    <VirtualTable
      columns={columns}
      data={data}
      rowHeight={40}
      containerHeight={350}
      overscanRowCount={8}
    />
  </Box>
);

ScrollToIndex

In certain scenarios, consumers may need to programmatically scroll to a specific row in the virtualized table. The scrollToIndex function provided by the tanstack/react-virtual virtualizer can be accessed through the onVirtualizerReady callback. This callback exposes a minimal virtualizer instance, allowing access to the scrollToIndex method. In the future, if additional utilities are needed, they can be exposed through this instance as well.

const [data, setData] = React.useState([]);
const [virtualizer, setVirtualizer] = React.useState(null);
const [scrollIndex, setScrollIndex] = React.useState(null);
const [loading, setLoading] = React.useState(true);

const isSafari = /^((?!chrome|android).)*safari/i.test(typeof navigator !== "undefined" ? navigator.userAgent : "");

React.useEffect(() => {
  fetch("https://randomuser.me/api/?results=200")
    .then((response) => response.json())
    .then((data) => {
      setData(data.results);
      setLoading(false);
    })
    .catch((error) => {
      console.log(error);
      setLoading(false);
    });
}, []);

const basicColumnDef = [
  {
    header: "FirstName",
    accessorKey: "name.first",
    size: 150,
  },
  {
    header: "LastName",
    accessorKey: "name.last",
    size: 150,
  },
  {
    header: "Age",
    accessorKey: "dob.age",
    size: 80,
  },
  {
    header: "Phone",
    accessorKey: "phone",
    size: 200,
  },
  {
    header: "Country",
    accessorKey: "location.country",
    size: 150,
  },
];
const columns = React.useMemo(() => basicColumnDef, []);
const handleScroll = () => {
  const randomIndex = Math.floor(Math.random() * data.length);
  setScrollIndex(randomIndex); // Update state to show in button

  virtualizer?.scrollToIndex(randomIndex, {
    align: "center",
    behavior: isSafari ? "auto" : "smooth",
  });
};
if (loading) {
  return (
    <Box className="flex justify-center w-full p-4 h-[200px]">
      <Loading size="48" />
    </Box>
  );
}
return (
  <Box className="flex flex-col items-center gap-4 w-full">
    <Button onClick={handleScroll}>
      {scrollIndex !== null
        ? `Scroll to Row ${scrollIndex}`
        : "Scroll to a Random Row"}
    </Button>
    <VirtualTable
      columns={columns}
      data={data}
      rowHeight={40}
      containerHeight={350}
      overscanRowCount={8}
      onVirtualizerReady={(v) => setVirtualizer(v)}
    />
  </Box>
);

Style API

Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.

Please refer to the Style API documentation for more insights.

VirtualTable parts

const [data, setData] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const basicColumnDef = [
  {
    header: "FirstName",
    accessorKey: "name.first",
    size: 150,
  },
  {
    header: "LastName",
    accessorKey: "name.last",
    size: 150,
  },
  {
    header: "Age",
    accessorKey: "dob.age",
    size: 80,
  },
  {
    header: "Phone",
    accessorKey: "phone",
    size: 200,
  },
  {
    header: "Country",
    accessorKey: "location.country",
    size: 150,
  },
];
React.useEffect(() => {
  fetch("https://randomuser.me/api/?results=200")
    .then((response) => response.json())
    .then((data) => {
      setLoading(false);
    })
    .catch((error) => {
      console.log(error);
      setLoading(false);
    });
}, []);
const columns = React.useMemo(() => basicColumnDef, []);
if (loading) {
  return (
    <Box className="flex justify-center w-full p-4 h-[200px]">
      <Loading size="48" />
    </Box>
  );
}
return (
  <Box className="flex align-center justify-center">
    <VirtualTable
      classNames={{
        tableContainer: "max-h-96 overflow-auto",
        table: "bg-caution-secondary",
        // Styles the table header
        header: "sticky top-0 z-10",
        headerRow: "bg-gray-400 ",
        headerCell: "bg-palette-violet-background",
        headerContentWrapper: "p-1 pl-2 bg-palette-blue-background-active",
        headerContent: "font-stronger text-constant-white",
        sortIconsWrapper: "bg-accent-secondary border-2",
        sortIcon: "fill-positive",
        // Styles the table body row
        row: "border-input-active",
        cell: "font-stronger text-caution-secondary",
      }}
      columns={columns}
      data={data}
      rowHeight={40}
      containerHeight={350}
      overscanRowCount={8}
    />
  </Box>
);
Stylable PartsDescription
tableContainerWrapper that holds the entire table, ensuring proper layout, scrolling, and responsiveness
tableComponent for displaying data in rows and columns with a consistent design and interactions
headerSection that contains the header row with all header cells, defining column titles and interactions.
headerRowContains all header cells, defining column titles, sorting, actions etc.,
headerCellContainer that holds header content and aligns with the table’s structure.
headerContentWrapperContainer that holds the column header content, ensuring proper alignment, spacing, and interaction
headerContentMain content inside a column header, typically including text, icons, or actions
sortIconsWrapperContainer that holds sorting icons.
sortIconVisually indicate and enable sorting of column data.
bodyThe main section that holds the data rows. It follows structured spacing, typography.
rowRow represents a single record or data entry, structured across column. It typically includes text, icons etc.,
cellCell is a single unit of content within a row and column.