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:
Meet the Code
This is an example to get you started with Infinite Table with minimal setup.
import * as React from 'react'; import '@infinite-table/infinite-react/index.css'; import { InfiniteTable, DataSource, InfiniteTablePropColumns, } from '@infinite-table/infinite-react'; type Person = { Id: number; FirstName: string; Age: number; }; const columns: InfiniteTablePropColumns<Person> = { id: { // specifies which field from the data source // should be rendered in this column field: 'Id', type: 'number', defaultWidth: 80, }, firstName: { field: 'FirstName', header: 'First Name', }, age: { field: 'Age', type: 'number' }, }; const data: Person[] = [ { Id: 1, FirstName: 'Bob', Age: 3, }, { Id: 2, FirstName: 'Alice', Age: 50, }, { Id: 3, FirstName: 'Bill', Age: 5, }, ]; export default function App() { return ( <DataSource<Person> data={data} primaryKey="Id"> <InfiniteTable<Person> columns={columns} /> </DataSource>
Note
Don't forget to import the CSS to see the component in action!
import '@infinite-table/infinite-react/index.css';
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 theInfiniteTable
component - it controls whichdata
the table is renderingInfiniteTable
- the actual virtualized table component - needs to be inside aDataSource
(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<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';
Learn about our TypeScript typings
Read more about how to use our TypeScript types.
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
'use client'; import { InfiniteTable, DataSource, GroupRowsState, InfiniteTablePropColumnTypes, DataSourcePropRowSelection_MultiRow, InfiniteTableColumn, InfiniteTableColumnRenderValueParam, DataSourcePropAggregationReducers, DataSourceGroupBy, components, DataSourcePropFilterValue, } from '@infinite-table/infinite-react'; import * as React from 'react'; import { useState } from '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: 100, renderValue: ({ value }) => value, }, salary: { header: 'Compensation', field: 'salary', type: 'number', defaultWidth: 210, }, 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, defaultFilterable: false, // 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 currentGroupBy = groupBy[groupBy.length - 1]; if (currentGroupBy?.field === '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, }; const defaultFilterValue: DataSourcePropFilterValue<Developer> = [ { field: 'age', filter: { operator: 'gt', type: 'number', value: null, }, }, ]; const domProps = { style: { minHeight: '50vh', height: 600, margin: 5, }, }; export default function App() { const [{ min, max }, setMinMax] = useState({ min: 0, max: 0 }); const columns = React.useMemo(() => { const cols = getColumns(); if (cols.salary) { cols.salary.render = ({ renderBag, 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 ( <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" defaultFilterValue={defaultFilterValue} filterMode="local" 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, }} domProps={domProps} 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 license 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.
import '@infinite-table/infinite-react/index.css'; import { InfiniteTable, DataSource, 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', 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>
More on Licensing
Read more about our licensing model and how you can use Infinite Table.
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.