Getting Started

Infinite Table is a UI component for data virtualization - helps you display huge datasets of tabular data.

Itโ€™s built specifically for React from the ground up and with performance in mind.

Installation

Infinite Table is available on the public npm registry - install it by running the following command:

Terminal
npm i @infinite-table/infinite-react

Meet the Code

View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';

import * as React from 'react';

type Person = {
  Id: number;
  FirstName: string;
  Age: number;
};

export default function App() {
  const columns = React.useMemo(() => {
    return {
      id: {
        // specifies which field from the data source
        // should be rendered in this column
        field: 'Id',
        type: 'number',
        sortable: true,
        width: 80,
      },

      firstName: {
        field: 'FirstName',
      },
      age: { field: 'Age', type: 'number' },
    } as Record<string, InfiniteTableColumn<Person>>;
  }, []);

  const data: Person[] = React.useMemo(
    () => [
      {
        Id: 1,
        FirstName: 'Bob',
        Age: 3,
      },
      {
        Id: 2,
        FirstName: 'Alice',
        Age: 50,
      },
      {
        Id: 3,
        FirstName: 'Bill',
        Age: 5,
      },
    ],
    [],
  );

  return (
    <DataSource<Person> data={data} primaryKey="Id">
      <InfiniteTable<Person> columnDefaultWidth={130} columns={columns} />
    </DataSource>

Using the Components

In the code snippet above, you notice weโ€™re using 2 components:

  • DataSource - this needs to be a parent (or ancestor, at any level) of the InfiniteTable component - it controls which data the table is rendering
  • InfiniteTable - the actual virtualized table component - needs to be inside a DataSource (can be at any level of nesting).

Both components are named exports of the @infinite-table/infinite-react package.

TypeScript Types

Our TypeScript types are published as part of the package, as named exports from the root of the package.

There are 2 components that you can use and import:

  • InfiniteTable
  • DataSource

Each of those has types provided for all the props it exposes, with the pattern of <COMPONENT_NAME><PROP_NAME>, so here are a few examples to clarify the rule:

import {
  InfiniteTablePropColumns, // or accessible as InfiniteTableProps['columns']
  // corresponding to the `columns` prop
  DataSourcePropGroupBy, // or accessible as DataSourceProps['groupBy']
  // corresponding to the `groupBy` prop
} from '@infinite-table/infinite-react';

Built for React from the ground-up

Infinite Table is built specifically for React and is fully declarative and fully typed. When you use Infinite Table, it feels at-home in your React application - every prop has both a controlled and uncontrolled version so you get full control over every area of the component.

This is an example of how you might configure InfiniteTable in a real-world application and puts together several functionalities:

  • grouping
  • aggregation
  • pinned columns
  • sorting
  • multiple selection
  • custom cell rendering
View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  GroupRowsState,
  InfiniteTablePropColumnTypes,
  DataSourcePropRowSelection_MultiRow,
} from '@infinite-table/infinite-react';
import type {
  InfiniteTableColumn,
  InfiniteTableColumnRenderValueParam,
  DataSourcePropAggregationReducers,
  DataSourceGroupBy,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';
import { components } from '@infinite-table/infinite-react';
const { CheckBox } = components;

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  country: string;
  city: string;
  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';
  hobby: string;
  salary: number;
  age: number;
};

const showLogo =
  typeof window === 'undefined'
    ? true
    : window.location.host.startsWith('localhost:') ||
      window.location.host.startsWith('infinite-table.com');

const avgReducer = {
  initialValue: 0,
  reducer: (acc: number, sum: number) => acc + sum,
  done: (value: number, arr: any[]) =>
    arr.length ? Math.floor(value / arr.length) : 0,
};
const aggregationReducers: DataSourcePropAggregationReducers<Developer> = {
  salary: {
    field: 'salary',

    ...avgReducer,
  },
  age: {
    field: 'age',
    ...avgReducer,
  },
  currency: {
    field: 'currency',
    initialValue: new Set<string>(),
    reducer: (acc: Set<string>, value: string) => {
      acc.add(value);
      return acc;
    },
    done: (value: Set<string>) => {
      return value.size > 1 ? 'Mixed' : value.values().next().value;
    },
  },
  canDesign: {
    field: 'canDesign',

    initialValue: false,
    reducer: (acc: boolean | null, value: 'yes' | 'no') => {
      if (acc === null) {
        return acc;
      }
      if (acc === false && value === 'yes') {
        return null;
      }
      if (acc === true && value === 'no') {
        return null;
      }
      return acc;
    },
  },
};

const flags = {
  'United States': '๐Ÿ‡บ๐Ÿ‡ธ',
  Canada: '๐Ÿ‡จ๐Ÿ‡ฆ',
  France: '๐Ÿ‡ซ๐Ÿ‡ท',
  Germany: '๐Ÿ‡ฉ๐Ÿ‡ช',
  'United Kingdom': '๐Ÿ‡ฌ๐Ÿ‡ง',
  'South Africa': '๐Ÿ‡ฟ๐Ÿ‡ฆ',
  'New Zealand': '๐Ÿ‡ณ๐Ÿ‡ฟ',
  Sweden: '๐Ÿ‡ธ๐Ÿ‡ช',
  China: '๐Ÿ‡จ๐Ÿ‡ณ',
  Brazil: '๐Ÿ‡ง๐Ÿ‡ท',
  Turkey: '๐Ÿ‡น๐Ÿ‡ท',
  Italy: '๐Ÿ‡ฎ๐Ÿ‡น',
  India: '๐Ÿ‡ฎ๐Ÿ‡ณ',
  Indonesia: '๐Ÿ‡ฎ๐Ÿ‡ฉ',
  Japan: '๐Ÿ‡ฏ๐Ÿ‡ต',
  Argentina: '๐Ÿ‡ฆ๐Ÿ‡ท',
  'Saudi Arabia': '๐Ÿ‡ธ๐Ÿ‡ฆ',
  Mexico: '๐Ÿ‡ฒ๐Ÿ‡ฝ',
  'United Arab Emirates': '๐Ÿ‡ฆ๐Ÿ‡ช',
};
function getColumns(): Record<string, InfiniteTableColumn<Developer>> {
  return {
    age: {
      field: 'age',
      header: 'Age',
      type: 'number',
      defaultWidth: 80,
      renderValue: ({ value }) => value,
    },
    salary: {
      header: 'Compensation',
      field: 'salary',
      type: 'number',
      defaultWidth: 170,
    },
    currency: { field: 'currency', header: 'Currency', defaultWidth: 100 },
    preferredLanguage: {
      field: 'preferredLanguage',
      header: 'Programming Language',
    },

    canDesign: {
      defaultWidth: 135,
      field: 'canDesign',
      header: 'Design Skills',
      renderMenuIcon: false,
      renderValue: ({ value }) => {
        return (
          <div style={{ display: 'flex', alignItems: 'center' }}>
            <CheckBox
              defaultChecked={value === null ? null : value === 'yes'}
              domProps={{
                style: {
                  marginRight: 10,
                },
              }}
            />
            {value === null ? 'Some' : value === 'yes' ? 'Yes' : 'No'}
          </div>
        );
      },
    },
    country: {
      field: 'country',
      header: 'Country',
      renderValue: ({ value }) => {
        return (
          <span>
            {(flags as any)[value] || null} {value}
          </span>
        );
      },
    },
    firstName: { field: 'firstName', header: 'First Name' },
    stack: { field: 'stack', header: 'Stack' },

    city: { field: 'city', header: 'City' },
  };
}

// โ†’ 123.456,789
const groupColumn: InfiniteTableColumn<Developer> = {
  header: 'Grouping',
  field: 'firstName',
  defaultWidth: 250,
  renderSelectionCheckBox: true,

  // in this function we have access to collapsed info
  // and grouping info about the current row - see rowInfo.groupBy
  renderValue: ({
    value,
    rowInfo,
  }: InfiniteTableColumnRenderValueParam<Developer>) => {
    if (!rowInfo.isGroupRow) {
      return value;
    }
    const groupBy = rowInfo.groupBy || [];
    const collapsed = rowInfo.collapsed;
    const groupField = groupBy[groupBy.length - 1];

    if (groupField === 'age') {
      return `๐Ÿฅณ ${value}${collapsed ? ' ๐Ÿคทโ€โ™‚๏ธ' : ''}`;
    }

    return `${value}`;
  },
};

const defaultGroupRowsState = new GroupRowsState({
  //make all groups collapsed by default
  collapsedRows: true,
  expandedRows: [
    ['United States'],
    ['United States', 'backend'],
    ['France'],
    ['Turkey'],
  ],
});

const columnTypes: InfiniteTablePropColumnTypes<Developer> = {
  number: {
    align: 'end',
    style: () => {
      return {};
    },
    renderValue: ({ value, data, rowInfo }) => {
      return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency:
          rowInfo.isGroupRow && rowInfo.data?.currency === 'Mixed'
            ? 'USD'
            : data?.currency || 'USD',
      }).format(value);
    },
  },
};

const defaultRowSelection: DataSourcePropRowSelection_MultiRow = {
  selectedRows: [['United States'], ['India'], ['France'], ['Turkey']],
  deselectedRows: [['United States', 'frontend']],
  defaultSelection: false,
};

export default function App() {
  const [{ min, max }, setMinMax] = useState({ min: 0, max: 0 });
  const columns = React.useMemo(() => {
    const cols = getColumns();

    if (cols.salary) {
      const style: InfiniteTableColumn<Developer>['style'] = ({ value }) => {
        const increase: number = Math.abs(max - min);
        const percentage = ((value - min) / increase) * 100;
        const alpha = Number((percentage / 100).toPrecision(2)) - 0.2;

        const backgroundColor = `rgba(255, 0, 0, ${alpha})`;

        return { backgroundColor };
      };
      cols.salary.render = ({ renderBag, value, rowInfo }) => {
        const increase: number = Math.abs(max - min);
        const percentage = ((value - min) / increase) * 100;
        const alpha = Number((percentage / 100).toPrecision(2)) + 0.2;

        const backgroundColor = `rgba(255, 0, 0, ${alpha})`;
        return (
          <div
            style={{
              position: 'absolute',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'flex-end',
              top: 0,
              bottom: 0,
              left: 0,
              right: 0,
              backgroundColor,
            }}
          >
            {renderBag.all}
          </div>
        );
      };
    }

    return cols;
  }, [min, max]);

  const groupBy: DataSourceGroupBy<Developer>[] = React.useMemo(
    () => [
      {
        field: 'country',
      },
      { field: 'stack' },
    ],
    [],
  );

  const Link = ({
    children,
    href,
  }: {
    children: React.ReactNode;
    href: string;
  }) => {
    return (
      <a
        href={href}
        style={{
          textDecoration: 'underline',
          color: '#3bff7f',
          padding: 20,
          display: 'flex',
          alignItems: 'center',
        }}
      >
        {children}
      </a>
    );
  };
  return (
    <>
      {showLogo ? (
        <div style={{ display: 'flex' }}>
          <Link href="https://infinite-table.com">
            <img
              src="https://infinite-table.com/logo-infinite.svg"
              width={50}
              title="Infinite Logo"
              style={{ marginRight: 20 }}
            />
            Go Back Home
          </Link>

          <Link href="/docs/learn/getting-started#built-for-react-from-the-ground-up">
            View source
          </Link>
        </div>
      ) : null}
      <DataSource<Developer>
        data={dataSource}
        primaryKey="id"
        useGroupKeysForMultiRowSelection
        defaultRowSelection={defaultRowSelection}
        defaultSortInfo={{
          dir: -1,
          field: 'country',
        }}
        onDataArrayChange={(data) => {
          const min = Math.min(...data.map((data) => data.salary ?? 0));
          const max = Math.max(...data.map((data) => data.salary ?? 0));

          setMinMax({ min, max });
        }}
        defaultGroupRowsState={defaultGroupRowsState}
        aggregationReducers={aggregationReducers}
        groupBy={groupBy}
      >
        <InfiniteTable<Developer>
          groupRenderStrategy="single-column"
          defaultColumnPinning={{
            'group-by': true,
          }}
          defaultActiveRowIndex={0}
          groupColumn={groupColumn}
          licenseKey={process.env.NEXT_PUBLIC_INFINITE_LICENSE_KEY}
          columns={columns}
          columnTypes={columnTypes}
          columnDefaultWidth={150}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {

Licensing

You can use @infinite-table/infinite-react in 2 ways:

  • with a license - requests for licence quotations and additional quotations must be made by email to admin@infinite-table.com. After purchasing, you will receive a licenseKey which you will provide as a prop when you instantiate Infinite Table. This will make the Powered by Infinite Table footer go away.
  • without a license, but it will include a Powered by Infinite Table link in the table footer. This way you can use it for free in any product, but make sure the footer is always visible when Infinite Table is visible. For demo purposes, we donโ€™t show any license error for embeds in codesandbox.io - which are used throughout this demo site. Check the demo below to see the license footer in action.
Invalid License Demo
View Mode
Fork
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTableColumn } from '@infinite-table/infinite-react';
import * as React from 'react';

import { data, Person } from './data';

const columns: Record<string, InfiniteTableColumn<Person>> = {
  id: {
    // specifies which field from the data source
    // should be rendered in this column
    field: 'Id',
    type: 'number',
    sortable: true,
    defaultWidth: 80,
  },
  firstName: {
    field: 'FirstName',
  },
  age: { field: 'Age', type: 'number' },
};

export default function App() {
  return (
    <DataSource<Person> data={data} primaryKey="Id">
      <InfiniteTable<Person>
        licenseKey="<INVALID>"
        columnDefaultWidth={130}
        columns={columns}
      />
    </DataSource>

About the Docs

Weโ€™re grateful for the work done by the team behind reactjs.org and the new React documentation found at beta.reactjs.org - weโ€™ve built our documentation on their excellent work ๐Ÿ™ and weโ€™re grateful for that.

The documentation is versioned, and we will publish a new version of the documentation when there are any significant changes in the corresponding @infinite-table/infinite-react version.