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 Parts

Description

tableContainer

Wrapper that holds the entire table, ensuring proper layout, scrolling, and responsiveness

table

Component for displaying data in rows and columns with a consistent design and interactions

header

Section that contains the header row with all header cells, defining column titles and interactions.

headerRow

Contains all header cells, defining column titles, sorting, actions etc.,

headerCell

Container that holds header content and aligns with the table’s structure.

headerContentWrapper

Container that holds the column header content, ensuring proper alignment, spacing, and interaction

headerContent

Main content inside a column header, typically including text, icons, or actions

sortIconsWrapper

Container that holds sorting icons.

sortIcon

Visually indicate and enable sorting of column data.

body

The main section that holds the data rows. It follows structured spacing, typography.

row

Row represents a single record or data entry, structured across column. It typically includes text, icons etc.,

cell

Cell is a single unit of content within a row and column.