# Infinite Table Props

> Infinite Table Props Reference page with complete examples

Canonical page: https://infinite-table.com/docs/reference/infinite-table-props

In the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.

### debugId (`string`)

> The unique id to identify this InfiniteTable instance in devtools

If you have [Infinite Table DevTools extension](https://chromewebstore.google.com/detail/infinite-table-devtools-e/jpipjljbfffijmgiecljadbogfegejfa) installed, the current `<InfiniteTable />` instance will be picked up by the devtools with this specific name.

See [our blogpost on the devtools extension](https://infinite-table.com/blog/2025/05/12/the-first-devtools-for-a-datagrid.md) for more details.

### repeatWrappedGroupRows (`boolean|(rowInfo: InfiniteTableRowInfo<DATA_TYPE>) => boolean`)

> When enabled, and [`wrapRowsHorizontally`](https://infinite-table.com/docs/reference/infinite-table-props.md#wrapRowsHorizontally) is also enabled, if there is [grouping configured](https://infinite-table.com/docs/reference/datasource-props/index.md#groupBy) or if you're using tree data, the group/tree rows will be repeated at the top of each column set if the group/parent starts in the previous column set.

See related [`wrapRowsHorizontally`](https://infinite-table.com/docs/reference/infinite-table-props.md#wrapRowsHorizontally).

**Example: Horizontal Layout with repeated wrapped group rows**

```tsx
import '@infinite-table/infinite-react/index.css';
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';

import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' +
      `/developers1k-sql?`,
  )
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', header: 'ID', defaultWidth: 50 },

  country: { field: 'country', header: 'Country' },
  city: { field: 'city', header: 'City' },
  firstName: { field: 'firstName', header: 'First Name' },
  separator: {
    valueGetter: () => null,
    resizable: false,
    defaultWidth: 10,
    style: {
      background: 'var(--infinite-background)',
    },
  },
};

export default function HorizontalLayout() {
  const [repeatWrappedGroupRows, setRepeatWrappedGroupRows] =
    React.useState(false);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          marginBottom: 10,
        }}
      >
        <button
          onClick={() => {
            setRepeatWrappedGroupRows(!repeatWrappedGroupRows);
          }}
        >
          {repeatWrappedGroupRows ? 'Disable' : 'Enable'} Repeat Wrapped Group
          Rows
        </button>
      </div>
      <DataSource<Developer>
        primaryKey="id"
        data={dataSource}
        defaultGroupBy={[
          {
            field: 'country',
          },
          {
            field: 'city',
          },
        ]}
      >
        <InfiniteTable<Developer>
          debugId="horizontal-layout-repeat-wrapped-groups-example"
          wrapRowsHorizontally
          repeatWrappedGroupRows={repeatWrappedGroupRows}
          columns={columns}
          columnDefaultWidth={100}
          columnDefaultSortable={false}
        />
      </DataSource>
    </>
  );
}
```

**Example: Tree with horizontal Layout and repeated wrapped tree rows**

In this example, parent nodes are repeated conditionally: only top-level parent nodes are repeated when wrapping happens.

```tsx
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
} from '@infinite-table/infinite-react';

type FileSystemNode = {
  id: string;
  name: string;
  type: 'folder' | 'file';
  extension?: string;
  sizeInKB: number;
  children?: FileSystemNode[];
};

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { renderTreeIcon: true, field: 'name', header: 'Name' },
  type: { field: 'type', header: 'Type' },
  extension: { field: 'extension', header: 'Extension' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size (KB)' },
};

export default function App() {
  return (
    <TreeDataSource nodesKey="children" primaryKey="id" data={dataSource}>
      <TreeGrid
        columns={columns}
        wrapRowsHorizontally
        repeatWrappedGroupRows={(rowInfo) => {
          if (!rowInfo.isTreeNode) {
            return false;
          }
          return rowInfo.treeNesting === 0;
        }}
      />
    </TreeDataSource>
  );
}

const dataSource = () => {
  const nodes: FileSystemNode[] = [
    {
      id: '1',
      name: 'Documents',
      sizeInKB: 1200,
      type: 'folder',
      children: [
        {
          id: '10',
          name: 'Private',
          sizeInKB: 100,
          type: 'folder',
          children: [
            {
              id: '100',
              name: 'Report.docx',
              sizeInKB: 210,
              type: 'file',
              extension: 'docx',
            },
            {
              id: '101',
              name: 'Vacation.docx',
              sizeInKB: 120,
              type: 'file',
              extension: 'docx',
            },
            {
              id: '102',
              name: 'CV.pdf',
              sizeInKB: 108,
              type: 'file',
              extension: 'pdf',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      name: 'Desktop',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
          extension: 'txt',
        },
      ],
    },
    {
      id: '3',
      name: 'Media',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '30',
          name: 'Music',
          sizeInKB: 0,
          type: 'folder',
          children: [
            {
              id: '300',
              name: 'empty.mp3',
              sizeInKB: 0,
              type: 'file',
            },
            {
              id: '301',
              name: 'Hawaii Song.mp3',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp3',
            },
            {
              id: '302',
              name: 'Independence Day Song.mp3',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp3',
            },
            {
              id: '303',
              name: 'Pop Song.mp3',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp3',
            },
          ],
        },
        {
          id: '31',
          name: 'Videos',
          sizeInKB: 5400,
          type: 'folder',
          children: [
            {
              id: '310',
              name: 'Vacation.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
            },
            {
              id: '311',
              name: 'Honolulu.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
            },
            {
              id: '312',
              name: 'New York.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};
```

### wrapRowsHorizontally (`boolean`)

> Whether to wrap rows horizontally or not. Horizontal Layout is a very different approach to the normal grid layout and only useful in very advanced scenarios.

When this is set to `true`, rows will be wrapped horizontally to fit the container.

When horizontal layout is enabled in combination with grouping, you can also use the [`repeatWrappedGroupRows`](https://infinite-table.com/docs/reference/infinite-table-props.md#repeatWrappedGroupRows) property to repeat the group rows at the top of each column set - if the group starts in the previous column set.

When horizontal layout is enabled, rows will wrap and the existing columns will be repeated for each row-wrapping section - we will call them  column sets.

So for example when the DataGrid is configured with 3 columns and the DataSource has 25 rows, but only 10 rows fit in the vertical viewport, you will end up with 3 column-sets: the first with 10 rows, the second with the next 10 rows, and the third with the remaining 5 rows. The same columns are repeated for each column-set.

**Example: Horizontal Layout example**

```tsx
import '@infinite-table/infinite-react/index.css';
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';

import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' +
      `/developers1k-sql?`,
  )
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', header: 'ID', defaultWidth: 50 },
  firstName: { field: 'firstName', header: 'First Name' },
  age: { field: 'age', header: 'Age' },
};

export default function HorizontalLayout() {
  const [wrapRowsHorizontally, setWrapRowsHorizontally] = React.useState(false);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          marginBottom: 10,
        }}
      >
        <button
          onClick={() => {
            setWrapRowsHorizontally(!wrapRowsHorizontally);
          }}
        >
          {wrapRowsHorizontally ? 'Disable' : 'Enable'} Horizontal Layout
        </button>
      </div>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="horizontal-layout-example"
          wrapRowsHorizontally={wrapRowsHorizontally}
          columns={columns}
          columnDefaultWidth={100}
          columnDefaultSortable={false}
        />
      </DataSource>
    </>
  );
}
```

In the column rendering functions (both for header and cell rendering), you will have access to the `horizontalLayoutPageIndex` property. This is the index of the current horizontal layout page (the current column-set). `horizontalLayoutPageIndex` can either be `null`, when horizontal layout is disabled, or a number >= 0, when horizontal layout is enabled.

When using horizontal layout, columns can't be configured to have a flexible width. So don't specify [`columns.defaultFlex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex) for any column when horizontal layout is enabled.

**Example: Horizontal Layout example with column set index in header**

```tsx
import '@infinite-table/infinite-react/index.css';
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';

import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' +
      `/developers1k-sql?`,
  )
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const getColumnHeaderFor = (
  label: string,
): InfiniteTableColumn<Developer>['header'] => {
  return ({
    horizontalLayoutPageIndex,
  }: {
    horizontalLayoutPageIndex: number | null;
  }) => {
    return (
      <>
        {label}
        {horizontalLayoutPageIndex != null
          ? `(${horizontalLayoutPageIndex + 1})`
          : ''}
      </>
    );
  };
};
const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    defaultWidth: 80,
    header: getColumnHeaderFor('ID'),
  },
  firstName: { field: 'firstName', header: getColumnHeaderFor('Name') },
  age: { field: 'age', header: getColumnHeaderFor('Age') },
};

export default function HorizontalLayout() {
  const [wrapRowsHorizontally, setWrapRowsHorizontally] = React.useState(true);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          marginBottom: 10,
        }}
      >
        <button
          onClick={() => {
            setWrapRowsHorizontally(!wrapRowsHorizontally);
          }}
        >
          {wrapRowsHorizontally ? 'Disable' : 'Enable'} Horizontal Layout
        </button>
      </div>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="horizontal-layout-with-column-set-index-in-header-example"
          wrapRowsHorizontally={wrapRowsHorizontally}
          columns={columns}
          columnDefaultWidth={100}
          columnDefaultSortable={false}
        />
      </DataSource>
    </>
  );
}
```

### components.RowDetail

> Component to use for rendering the row details section in the master-detail DataGrid. When specified, it makes InfiniteTable be a [master-detail DataGrid](https://infinite-table.com/docs/learn/master-detail/overview.md). For configuring the height of row details, see [`rowDetailHeight`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailHeight)

See related [`rowDetailRenderer`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailRenderer).

**Example: Basic master detail DataGrid example**

This example shows a master DataGrid with cities & countries.

The details for each city shows a DataGrid with developers in that city.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  useMasterRowInfo,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};
function RowDetail() {
  const rowInfo = useMasterRowInfo<City>()!;
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-component-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

const components = {
  RowDetail,
};

export default () => {
  return (
    <>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-component-example-2"
          domProps={domProps}
          columnDefaultWidth={150}
          columnMinWidth={50}
          columns={masterColumns}
          components={components}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### scrollStopDelay (`number`)

> The delay in milliseconds that the DataGrid waits until it considers scrolling to be stopped. Also used when lazy loading is to fetch the next batch of data.

This also determines when the [`onScrollStop`](https://infinite-table.com/docs/reference/infinite-table-props.md#onScrollStop) callback prop is called.

**Example: Scroll stop delay for lazy loading**

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useMemo } from 'react';

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 columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', header: 'ID', defaultWidth: 100 },
  salary: { field: 'salary', header: 'Salary' },
  age: { field: 'age', header: 'Age' },
  firstName: { field: 'firstName', header: 'First Name' },
  preferredLanguage: {
    field: 'preferredLanguage',
    header: 'Preferred Language',
  },
  lastName: { field: 'lastName', header: 'Last Name' },
  country: { field: 'country', header: 'Country' },
  city: { field: 'city', header: 'City' },
  currency: { field: 'currency', header: 'Currency' },
  stack: { field: 'stack', header: 'Stack' },
  canDesign: { field: 'canDesign', header: 'Can Design' },
  hobby: { field: 'hobby', header: 'Hobby' },
};

export default function App() {
  const lazyLoad = useMemo(() => ({ batchSize: 40 }), []);
  return (
    <DataSource<Developer>
      data={dataSource}
      primaryKey="id"
      lazyLoad={lazyLoad}
    >
      <InfiniteTable<Developer>
        debugId="scrollStopDelay-lazy-load-example"
        columns={columns}
        columnDefaultWidth={130}
        scrollStopDelay={50}
      />
    </DataSource>
  );
}

const dataSource: DataSourceData<Developer> = ({
  pivotBy,
  aggregationReducers,
  groupBy,

  lazyLoadStartIndex,
  lazyLoadBatchSize,
  groupKeys = [],
  sortInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }
  const startLimit: string[] = [];
  if (lazyLoadBatchSize && lazyLoadBatchSize > 0) {
    const start = lazyLoadStartIndex || 0;
    startLimit.push(`start=${start}`);
    startLimit.push(`limit=${lazyLoadBatchSize}`);
  }
  const args = [
    ...startLimit,
    pivotBy
      ? 'pivotBy=' + JSON.stringify(pivotBy.map((p) => ({ field: p.field })))
      : null,
    `groupKeys=${JSON.stringify(groupKeys)}`,
    groupBy
      ? 'groupBy=' + JSON.stringify(groupBy.map((p) => ({ field: p.field })))
      : null,
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,
    aggregationReducers
      ? 'reducers=' +
        JSON.stringify(
          Object.keys(aggregationReducers).map((key) => ({
            field: aggregationReducers[key].field,
            id: key,
            name: aggregationReducers[key].reducer,
          })),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');
  return fetch(
    process.env.NEXT_PUBLIC_BASE_URL + `/developers10k-sql?` + args,
  ).then((r) => r.json());
};
```

### headerOptions (`{alwaysReserveSpaceForSortIcon: boolean}`)

> Various header configurations for the DataGrid.

For now, it has the following properties:

 - [`headerOptions.alwaysReserveSpaceForSortIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#headerOptions.alwaysReserveSpaceForSortIcon)

**Example**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
  components,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

const { CheckBox } = components;

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'Location: City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [reserveSpaceForSortIcon, setReserveSpaceForSortIcon] = useState(true);
  return (
    <>
      <div style={{ color: 'var(--infinite-cell-color)', padding: 10 }}>
        <CheckBox
          checked={reserveSpaceForSortIcon}
          onChange={() => setReserveSpaceForSortIcon((prev) => !prev)}
        ></CheckBox>
        Reserve space for sort icon
      </div>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="sortIcon-reserve-space-example"
          headerOptions={{
            alwaysReserveSpaceForSortIcon: reserveSpaceForSortIcon,
          }}
          columns={columns}
          columnDefaultWidth={100}
          columnMinWidth={30}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### headerOptions.alwaysReserveSpaceForSortIcon (`boolean`)

> Whether to reserve space in the column header for the sort icon or not.

When this is set to `true`, the space for the sort icon is always reserved, even if the column is not currently sorted.

**Example**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
  components,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

const { CheckBox } = components;

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'Location: City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [reserveSpaceForSortIcon, setReserveSpaceForSortIcon] = useState(true);
  return (
    <>
      <div style={{ color: 'var(--infinite-cell-color)', padding: 10 }}>
        <CheckBox
          checked={reserveSpaceForSortIcon}
          onChange={() => setReserveSpaceForSortIcon((prev) => !prev)}
        ></CheckBox>
        Reserve space for sort icon
      </div>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="sortIcon-reserve-space-example"
          headerOptions={{
            alwaysReserveSpaceForSortIcon: reserveSpaceForSortIcon,
          }}
          columns={columns}
          columnDefaultWidth={100}
          columnMinWidth={30}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### rowDetailRenderer (`(rowInfo: InfiniteTableRowInfo<DATA_TYPE>) => ReactNode`)

> When specified, it makes InfiniteTable be a [master-detail DataGrid](https://infinite-table.com/docs/learn/master-detail/overview.md). For configuring the height of row details, see [`rowDetailHeight`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailHeight). See related [`components.RowDetail`](https://infinite-table.com/docs/reference/infinite-table-props.md#components.RowDetail).

It's an alternative to using [`components.RowDetail`](https://infinite-table.com/docs/reference/infinite-table-props.md#components.RowDetail).

This function is called with the [rowInfo](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) the user expands to see details for.

Using this function, you can render another DataGrid or any other custom content.

Make sure you have a column with the `renderRowDetailIcon: true` flag set. [`columns.renderRowDetailIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderRowDetailIcon) on a column makes the column display the row details expand icon.

Without this flag, no column will have the expand icon, and the master-detail functionality will not work.

To configure the height of the row details section, use the [`rowDetailHeight`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailHeight) prop.

For rendering some row details as already expanded, see [`defaultRowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultRowDetailState).

**Example: Basic master detail DataGrid example**

This example shows a master DataGrid with cities & countries.

The details for each city shows a DataGrid with developers in that city.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableRowInfo,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};
function renderDetail(rowInfo: InfiniteTableRowInfo<City>) {
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

export default () => {
  return (
    <>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-example-2"
          domProps={domProps}
          columnDefaultWidth={150}
          columnMinWidth={50}
          columns={masterColumns}
          rowDetailRenderer={renderDetail}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### showColumnFilters (`boolean`)

> Whether to show the column filters or not (only applicable when the `<DataSource>` is configured with filtering - either with [`defaultFilterValue`](https://infinite-table.com/docs/reference/datasource-props/index.md#defaultFilterValue) or [`filterValue`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterValue)).

When the `<DataSource>` is configured with [`defaultFilterValue`](https://infinite-table.com/docs/reference/datasource-props/index.md#defaultFilterValue), the column filters will be shown by default. Specify this prop as `false` to hide the column filters.

**Example: Controling the visibility of column filters**

```tsx
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

const data: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  firstName: {
    field: 'firstName',
  },
  stack: { field: 'stack' },
  currency: { field: 'currency', defaultFilterable: false },
};

export default () => {
  const [showColumnFilters, setShowColumnFilters] = React.useState(true);
  return (
    <>
      <React.StrictMode>
        <div style={{ paddingBlock: 10 }}>
          <button onClick={() => setShowColumnFilters(!showColumnFilters)}>
            Toggle visibility of column filters
          </button>
        </div>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          defaultFilterValue={[]}
          filterDelay={0}
          filterMode="local"
        >
          <InfiniteTable<Developer>
            debugId="column-filters-visibility-example"
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
            showColumnFilters={showColumnFilters}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

### defaultRowDetailState (`RowDetailState`)

> Specifies the default expanded/collapsed state of row details.

For the controlled version, see [`rowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailState).

If [`isRowDetailExpanded`](https://infinite-table.com/docs/reference/infinite-table-props.md#isRowDetailExpanded) is specified, it has the last word in deciding if a row detail is expanded or not, so it overrides the `defaultRowDetailState`.

**Example: Master detail DataGrid with some row details expanded by default**

Some of the rows in the master DataGrid are expanded by default.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableRowInfo,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};

function renderDetail(rowInfo: InfiniteTableRowInfo<City>) {
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-default-expanded-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

const defaultRowDetailState = {
  collapsedRows: true as const,
  expandedRows: [39, 54],
};

export default () => {
  return (
    <>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-default-expanded-example-2"
          domProps={domProps}
          columnDefaultWidth={150}
          defaultRowDetailState={defaultRowDetailState}
          columnMinWidth={50}
          columns={masterColumns}
          rowDetailHeight={200}
          rowDetailRenderer={renderDetail}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### isRowDetailExpanded (`(rowInfo: InfiniteTableRowInfo) => boolean`)

> This function ultimately decides if a row detail is expanded or not.

This function is meant for very advanced scenarios. For common use-cases, you'll probably use [`rowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailState) and [`defaultRowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultRowDetailState).

If `isRowDetailExpanded` is specified, it overrides [`rowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailState)/[`defaultRowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultRowDetailState).

### isRowDetailEnabled (`(rowInfo: InfiniteTableRowInfo<DATA_TYPE>) => boolean`)

> Decides on a per-row basis if the row details are enabled or not. See [Master Detail](https://infinite-table.com/docs/learn/master-detail/overview.md) for more information.

This function is called with the [rowInfo](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) and should return a `boolean` value.

It's useful when you don't want to show the row detail for some rows.

**Example: Master detail DataGrid with some row not having details**

All the odd rows don't have details.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableRowInfo,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};
function renderDetail(rowInfo: InfiniteTableRowInfo<City>) {
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-per-row-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

const isRowDetailEnabled = (rowInfo: InfiniteTableRowInfo<City>) => {
  return rowInfo.indexInAll % 2 === 0;
};

export default () => {
  return (
    <>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-per-row-example-2"
          domProps={domProps}
          columnDefaultWidth={150}
          columnMinWidth={50}
          columns={masterColumns}
          isRowDetailEnabled={isRowDetailEnabled}
          rowDetailRenderer={renderDetail}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### rowDetailState (`RowDetailState`)

> Specifies the expanded/collapsed state of row details.

For the uncontrolled version, see [`defaultRowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultRowDetailState).

When you use this controlled property, make sure you pair it with the [`onRowDetailStateChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onRowDetailStateChange) callback to update it.

If [`isRowDetailExpanded`](https://infinite-table.com/docs/reference/infinite-table-props.md#isRowDetailExpanded) is specified, it has the final say in deciding if a row detail is expanded or not, so it overrides the `rowDetailState` and [`defaultRowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultRowDetailState).

**Example: Master detail DataGrid with some row details expanded by default**

Some of the rows in the master DataGrid are expanded by default.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableRowInfo,
  RowDetailStateObject,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};

const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};
function renderDetail(rowInfo: InfiniteTableRowInfo<City>) {
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-controlled-expanded-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

export default () => {
  const [rowDetailState, setRowDetailState] = React.useState<
    RowDetailStateObject<any>
  >({
    collapsedRows: true as const,
    expandedRows: [39, 54],
  });

  return (
    <>
      <code
        style={{
          padding: 10,
          color: 'var(--infinite-cell-color)',
          background: 'var(--infinite-background)',
          maxHeight: 200,
          overflow: 'auto',
        }}
      >
        <pre>Row detail state: {JSON.stringify(rowDetailState, null, 2)}</pre>
      </code>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-controlled-expanded-example-2"
          domProps={domProps}
          onReady={({ api }) => {
            console.log(api.rowDetailApi);
          }}
          columnDefaultWidth={150}
          rowDetailState={rowDetailState}
          onRowDetailStateChange={(rowDetailState) => {
            setRowDetailState(rowDetailState.getState());
          }}
          columnMinWidth={50}
          columns={masterColumns}
          rowDetailHeight={200}
          rowDetailRenderer={renderDetail}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### onRowDetailStateChange (`(rowDetailState: RowDetailState, {expandRow, collapseRow}) => void`)

> Called when the expand/collapse state of row details changes.

You can use this function prop to update the [`rowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailState) prop or simply to listen to changes in the row details state.

This function is called with an instance of the [`RowDetailState`](https://infinite-table.com/docs/reference/type-definitions/index.md#RowDetailState). If you want to get the object behind it, simply call `rowDetailState.getState()`.

Both the `RowDetailState` instance and the state object (literal) are valid values you can pass to the [`rowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailState).

The second parameter of this function is an object with `expandRow` and `collapseRow` properties, which contain the primary key of either the last expanded or the last collapsed row.

For example, if the user is expanding row `3`, the object will be `{expandRow: 3, collapseRow: null}`.
Next, if the user collapses row `5`, the object will be `{expandRow: null, collapseRow: 5}`.

This makes it easy for you to know which action was taken and on which row.

See related [`rowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailState) and [`defaultRowDetailState`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultRowDetailState).

**Example: Master detail DataGrid with listener to the row expand/collapse state change**

Some of the rows in the master DataGrid are expanded by default.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableRowInfo,
  RowDetailStateObject,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};

const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};
function renderDetail(rowInfo: InfiniteTableRowInfo<City>) {
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-controlled-expanded-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

export default () => {
  const [rowDetailState, setRowDetailState] = React.useState<
    RowDetailStateObject<any>
  >({
    collapsedRows: true as const,
    expandedRows: [39, 54],
  });

  return (
    <>
      <code
        style={{
          padding: 10,
          color: 'var(--infinite-cell-color)',
          background: 'var(--infinite-background)',
          maxHeight: 200,
          overflow: 'auto',
        }}
      >
        <pre>Row detail state: {JSON.stringify(rowDetailState, null, 2)}</pre>
      </code>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-controlled-expanded-example-2"
          domProps={domProps}
          onReady={({ api }) => {
            console.log(api.rowDetailApi);
          }}
          columnDefaultWidth={150}
          rowDetailState={rowDetailState}
          onRowDetailStateChange={(rowDetailState) => {
            setRowDetailState(rowDetailState.getState());
          }}
          columnMinWidth={50}
          columns={masterColumns}
          rowDetailHeight={200}
          rowDetailRenderer={renderDetail}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### rowDetailCache (`boolean|number`)
> Controls the caching of detail DataGrids. By default, caching is disabled.

It can be one of the following:

- `false` - caching is disabled - this is the default
- `true` - enables caching for all detail DataGrids
- `number` - the maximum number of detail DataGrids to keep in the cache. When the limit is reached, the oldest detail DataGrid will be removed from the cache.

**Example: Master detail DataGrid with caching for 5 detail DataGrids**

This example will cache the last 5 detail DataGrids - meaning they won't reload when you expand them again.
You can try collapsing a row and then expanding it again to see the caching in action - it won't reload the data.
But when you open up a row that hasn't been opened before, it will load the data from the remote location.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableRowInfo,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};

const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};
function renderDetail(rowInfo: InfiniteTableRowInfo<City>) {
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-caching-with-default-expanded-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

const defaultRowDetailState = {
  collapsedRows: true as const,
  expandedRows: [39, 54],
};

export default () => {
  return (
    <>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-caching-with-default-expanded-example-2"
          domProps={domProps}
          columnDefaultWidth={150}
          defaultRowDetailState={defaultRowDetailState}
          columnMinWidth={50}
          columns={masterColumns}
          rowDetailCache={5}
          rowDetailHeight={200}
          rowDetailRenderer={renderDetail}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then(
      (data: Developer[]) =>
        new Promise((resolve) => {
          setTimeout(() => {
            resolve(data);
          }, 500);
        }),
    );
};
```

### rowDetailHeight (`number|CSSVar|(rowInfo)=>number`)

> Controls the height of the row details section, in master-detail DataGrids.

The default value is `300` pixels.

This can be a number, a string (the name of a CSS variable - eg `--detail-height`), or a function. When a function is defined, it's called with the [rowInfo](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) object for the corresponding row.

**Example: Master detail DataGrid with custom detail height**

In this example we configure the height of row details to be 200px.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableRowInfo,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};
function renderDetail(rowInfo: InfiniteTableRowInfo<City>) {
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-custom-detail-height-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

const defaultRowDetailState = {
  collapsedRows: true as const,
  expandedRows: [39, 54],
};

export default () => {
  return (
    <>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-custom-detail-height-example-2"
          domProps={domProps}
          columnDefaultWidth={150}
          defaultRowDetailState={defaultRowDetailState}
          columnMinWidth={50}
          columns={masterColumns}
          rowDetailHeight={200}
          rowDetailRenderer={renderDetail}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### activeCellIndex (`[number,number] | null`)

> Specifies the active cell for keyboard navigation. This is a controlled prop. See the [Keyboard Navigation](https://infinite-table.com/docs/learn/keyboard-navigation/navigating-cells.md) page for more details.

See [`defaultActiveCellIndex`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultActiveCellIndex) for the uncontrolled version of this prop and [`keyboardNavigation`](https://infinite-table.com/docs/reference/infinite-table-props.md#keyboardNavigation) for the keyboard navigation behavior.

Use the [`onActiveCellIndexChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onActiveCellIndexChange) callback to be notified when the active cell changes.

`null` is a valid value, and it means no cell is currently rendered as active. Especially useful for controlled scenarios, when you need ultimate control over the behavior of keyboard navigation.

**Example: Controlled keyboard navigation for cells**

This example starts with cell `[2,0]` already active.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' +
      `/developers1k-sql?`,
  )
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardNavigationForCells() {
  const [activeCellIndex, setActiveCellIndex] = React.useState<
    [number, number]
  >([2, 0]);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
        }}
      >
        Current active cell: {activeCellIndex[0]}, {activeCellIndex[1]}.
      </div>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="navigating-cells-controlled-example"
          activeCellIndex={activeCellIndex}
          onActiveCellIndexChange={setActiveCellIndex}
          columns={columns}
        />
      </DataSource>
    </>
  );
}
```

### activeRowIndex (`number | null`)

> Specifies the active row for keyboard navigation. This is a controlled prop. See the [Keyboard Navigation](https://infinite-table.com/docs/learn/keyboard-navigation/navigating-rows.md) page for more details.

See [`defaultActiveRowIndex`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultActiveRowIndex) for the uncontrolled version of this prop and [`keyboardNavigation`](https://infinite-table.com/docs/reference/infinite-table-props.md#keyboardNavigation) for the keyboard navigation behavior.

Use the [`onActiveRowIndexChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onActiveRowIndexChange) callback to be notified when the active row changes.

`null` is a valid value, and it means no row is currently rendered as active. Especially useful for controlled scenarios, when you need ultimate control over the behavior of keyboard navigation.

**Example: Controlled keyboard navigation for rows**

This example starts with row at index `2` already active.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' +
      `/developers1k-sql?`,
  )
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardNavigationForRows() {
  const [activeRowIndex, setActiveRowIndex] = React.useState(2);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
        }}
      >
        Current active row: {activeRowIndex}.
      </div>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="navigating-rows-controlled-example"
          keyboardNavigation="row"
          activeRowIndex={activeRowIndex}
          onActiveRowIndexChange={setActiveRowIndex}
          columns={columns}
        />
      </DataSource>
    </>
  );
}
```

### autoSizeColumnsKey (`number|string|{key,includeHeader,columnsToSkip,columnsToResize}`)

> Controls auto-sizing of columns.

Here is a list of possible values for `autoSizeColumnsKey`:

- `string` or `number` - when the value is changing, all columns will be auto-sized.

- an object with a `key` property (of type `string` or `number`) - whenever the `key` changes, the columns will be auto-sized. Specifying an object for `autoSizeColumnsKey` gives you more control over which columns are auto-sized and if the size measurements include the header or not.

When an object is used, the following properties are available:

- `key` - mandatory property, which, when changed, triggers the update
- `includeHeader` - optional boolean, - decides whether the header will be included in the auto-sizing calculations. If not specified, `true` is assumed.
- `columnsToSkip` - a list of column ids to skip from auto-sizing. If this is used, all columns except those in the list will be auto-sized.
- `columnsToResize` - the list of column ids to include in auto-sizing. If this is used, only columns in the list will be auto-sized.

**Example: Auto-sizing columns**

```tsx
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id' },
  firstName: { field: 'firstName' },
  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },
  country: { field: 'country' },
  age: { field: 'age', type: 'number' },
  salary: { field: 'salary', type: 'number' },
  currency: { field: 'currency', type: 'number' },
};

export default function GroupByExample() {
  const [key, setKey] = React.useState(0);
  const [includeHeader, setIncludeHeader] = React.useState(false);

  const autoSizeColumnsKey = React.useMemo(() => {
    return {
      includeHeader,
      key,
    };
  }, [key, includeHeader]);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          background: 'var(--infinite-background)',
        }}
      >
        <label>
          <input
            checked={includeHeader}
            type={'checkbox'}
            onChange={(e) => {
              setIncludeHeader(e.target.checked);
            }}
          />{' '}
          Include header
        </label>

        <button
          style={{
            margin: 10,
            padding: 10,
            borderRadius: 5,
            border: '2px solid magenta',
          }}
          onClick={() => {
            setKey((key) => key + 1);
          }}
        >
          Click to auto-size
        </button>
      </div>

      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="autoSizeColumnsKey-example"
          autoSizeColumnsKey={autoSizeColumnsKey}
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

When auto-sizing takes place, [`onColumnSizingChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnSizingChange) is called with the new column sizes. If you use controlled [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing), make sure you update its value accordingly.

When columns are auto-sized, keep in mind that only visible (rendered) rows are taken into account - so if you scroll new rows into view, auto-sizing columns may result in different column sizes.

In the same logic, keep in mind that by default columns are also virtualized (controlled by [`virtualizeColumns`](https://infinite-table.com/docs/reference/infinite-table-props.md#virtualizeColumns)), not only rows, so only visible columns are auto-sized (in case you have more columns, the columns that are not currently visible do not change their sizes).

### columnDefaultEditable (`boolean`)

> Specifies whether columns are editable by default.

To enable editing globally, you can use this boolean prop on the `InfiniteTable` component. It will enable the editing on all columns.

Or you can be more specific and choose to make individual columns editable via the [column.defaultEditable](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultEditable) prop.

In addition to the props already in discussion, you can use the [`editable`](https://infinite-table.com/docs/reference/infinite-table-props.md#editable) prop on the `InfiniteTable` component. This overrides all other properties and when it is defined, is the only source of truth for whether something is editable or not.

By default, double-clicking an editable cell will show the cell editor. You can prevent this by returning `{preventEdit: true}` from the [onCellDoubleClick](https://infinite-table.com/docs/reference/infinite-table-props.md#onCellDoubleClick) function prop.

**Example**

All columns are configured to not be editable, except the `salary` column.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  currency: string;
  stack: string;
  hobby: string;
  salary: string;
};
const dataSource: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    currency: 'USD',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'USD 1000',
  },
  {
    id: 2,
    firstName: 'Jane',
    currency: 'EUR',
    stack: 'backend',
    hobby: 'reading',
    salary: 'EUR 2000',
  },
  {
    id: 3,
    firstName: 'Jack',
    currency: 'GBP',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'GBP 3000',
  },
  {
    id: 4,
    firstName: 'Jill',
    currency: 'USD',
    stack: 'backend',
    hobby: 'reading',
    salary: 'USD 4000',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },

  salary: {
    // the only editable column
    defaultEditable: true,

    defaultWidth: 320,
    field: 'salary',
    header: 'Salary - edit accepts numbers only',
    style: { color: 'tomato' },
    getValueToEdit: ({ value }) => {
      return parseInt(value.substr(4), 10);
    },
    getValueToPersist: ({ value, data }) => {
      return `${data!.currency} ${parseInt(value, 10)}`;
    },
  },

  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  currency: {
    field: 'currency',
    header: 'Currency',
  },
};

export default function InlineEditingExample() {
  const shouldAcceptEdit = ({ value }: { value: any }) => {
    return parseInt(value, 10) == value;
  };

  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="global-should-accept-edit-example"
          columns={columns}
          columnDefaultEditable={false}
          shouldAcceptEdit={shouldAcceptEdit}
        />
      </DataSource>
    </>
  );
}
```

### columnDefaultSortable (`boolean`)

> Specifies whether columns are sortable by default.

This property is overriden by (in this order) the following props:

- [`columnTypes.defaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.defaultSortable)
- [`column.defaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.defaultSortable)
- [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable)

When specified, [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable) overrides all other properties and is the only source of truth for whether something is sortable or not.

This property does not apply for group columns, since for sorting, group columns generally depend on the columns they are grouping.

In some cases, you can have group columns that group by fields that are not bound to actual columns, so for determining sorting for group columns, use one of the following props:

- [`columnTypes.defaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.defaultSortable)
- [`column.defaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.defaultSortable)
- [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable)

### sortable (`boolean | ({column, columns, api, columnApi}) => boolean`)

> This prop is the ultimate source of truth on whether (and which) columns are sortable.

This property overrides all the following props:

- [`columnDefaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultSortable) (this is the base value, overriden by all other props in this list, in this order)
- [`columnTypes.defaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.defaultSortable)
- [`column.defaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.defaultSortable)

The [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable) prop is designed to be used for highly advanced scenarios, where you need to have ultimate control over which columns are sortable and which are not - in this case, you will want to declare [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable) as a function, which returns `true/false` for every column.

### columnDefaultWidth (`number`)

> Specifies the a default width for all columns.

If a column is explicitly sized via [column.defaultWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth), [column.defaultFlex](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex), [`columnSizing.width`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.width) (or [`defaultColumnSizing.width`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing.width)), that will be used instead.

Use [`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth) to set a minimum width for all columns.
Use [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth) to set a maximum width for all columns.

[`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth) and [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth) will be very useful once flex column sizing lands.

**Example**

```ts files=["columnDefaultWidth-example.page.tsx","data.ts"]

```

### columnHeaderHeight (`number`)

> The height of the column header.

This only refers to the height of the header label - so if you have another row in the column header, for filters, the filters will also have this height. Also, for column groups, each additional group will have this height.

**Example**

The column header height is set to `60` pixels. The column filters will also pick up this height.

```ts files=["columnHeaderHeight-example.page.tsx","data.ts"]

```

### columnMaxWidth (`number`)

> Specifies the maximum width for all columns.

For specifying the minimum column width, see [`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth).

Maximum column width can be controlled more granularly via [`columnSizing.maxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.maxWidth), on a per column level.

**Example**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

const defaultColumnSizing: InfiniteTablePropColumnSizing = {
  firstName: { flex: 1 },
  country: { flex: 1 },
  city: { flex: 1 },
  salary: { flex: 2 },
};

export default function App() {
  return (
    <DataSource<Employee> data={dataSource} primaryKey="id">
      <InfiniteTable<Employee>
        debugId="columnMaxWidth-example"
        columns={columns}
        columnMaxWidth={200}
        defaultColumnSizing={defaultColumnSizing}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### columnMinWidth (`number`)

> Specifies the minimum width for all columns.

For specifying the maximum column width, see [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth).

Minimum column width can be controlled more granularly via [`columnSizing.minWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.minWidth) or [`columns.minWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.minWidth), on a per column level.

**Example**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

const defaultColumnSizing: InfiniteTablePropColumnSizing = {
  city: { flex: 1 },
  salary: { flex: 2 },
};

export default function App() {
  return (
    <DataSource<Employee> data={dataSource} primaryKey="id">
      <InfiniteTable<Employee>
        debugId="columnMinWidth-example"
        columns={columns}
        columnMinWidth={300}
        defaultColumnSizing={defaultColumnSizing}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### columnOrder (`string[]|true`)

> Defines the order in which columns are displayed in the component

For uncontrolled usage, see [`defaultColumnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnOrder).

When using this controlled prop, make sure you also listen to [`onColumnOrderChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnOrderChange)

See [Column Order](https://infinite-table.com/docs/learn/columns/column-order.md) for more details on ordering columns both programatically and via drag & drop.

The `columnOrder` array can contain identifiers that are not yet defined in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) Map or can contain duplicate ids. This is a feature, not a bug. We want to allow you to use the `columnOrder` in a flexible way so it can define the order of current and future columns.

Displaying the same column twice is a perfectly valid use case.

**Example: Column order**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
    columnGroup: 'location',
  },

  city: {
    field: 'city',
    header: 'City',
    columnGroup: 'address',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
  department: {
    field: 'department',
    header: 'Department',
  },
  team: {
    field: 'team',
    header: 'Team',
  },
  company: { field: 'companyName', header: 'Company' },

  companySize: {
    field: 'companySize',
    header: 'Company Size',
  },
};

export default function App() {
  const [columnOrder, setColumnOrder] = useState<string[]>([
    'firstName',
    'country',
    'team',
    'company',
    'department',
    'companySize',
  ]);

  return (
    <>
      <div style={{ color: 'var(--infinite-cell-color)' }}>
        <p>
          Current column order:{' '}
          <code>
            <pre>{columnOrder.join(', ')}.</pre>
          </code>
        </p>
        <p>Drag column headers to reorder.</p>
      </div>

      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="columnOrder-example"
          columns={columns}
          columnOrder={columnOrder}
          onColumnOrderChange={setColumnOrder}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};
```

This prop can either be an array of strings (column ids) or the boolean `true`. When `true`, all columns present in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) object will be displayed, in the iteration order of the object keys.

**Example: Column order advanced example**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
    columnGroup: 'location',
  },

  city: {
    field: 'city',
    header: 'City',
    columnGroup: 'address',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
  department: {
    field: 'department',
    header: 'Department',
  },
  team: {
    field: 'team',
    header: 'Team',
  },
  company: { field: 'companyName', header: 'Company' },

  companySize: {
    field: 'companySize',
    header: 'Company Size',
  },
};

export default function App() {
  const [columnOrder, setColumnOrder] = useState<string[] | true>([
    'firstName',
    'country',
    'team',
    'company',
    'firstName',
    'not existing column',
    'companySize',
  ]);

  return (
    <>
      <div style={{ color: 'var(--infinite-cell-color)' }}>
        <p>
          Current column order:{' '}
          <code>
            <pre>{JSON.stringify(columnOrder)}.</pre>
          </code>
        </p>
        <p>
          Note: if the column order contains columns that don't exist in the
          `columns` definition, they will be skipped.
        </p>

        <button
          style={{
            border: '2px solid currentColor',
            padding: 5,
          }}
          onClick={() => {
            setColumnOrder(['firstName', 'country']);
          }}
        >
          Click to only show "firstName" and "country".
        </button>

        <button
          style={{
            border: '2px solid currentColor',
            padding: 5,
            marginLeft: 5,
          }}
          onClick={() => {
            setColumnOrder(true);
          }}
        >
          Click to reset column order.
        </button>
      </div>

      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="columnOrder-advanced-example"
          columns={columns}
          columnOrder={columnOrder}
          onColumnOrderChange={setColumnOrder}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};
```

Using [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder) in combination with [`columnVisibility`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnVisibility) is very powerful - for example, you can have a specific column order even for columns which are not visible at a certain moment, so when they will be made visible, you'll know exactly where they will be displayed.

### columns (`Record<string, InfiniteTableColumn<DATA_TYPE>>`)

> Describes the columns available in the component.

The following properties are available:

- [field](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.field)
- [defaultWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth)
- [defaultFlex](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex)
- [render](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render)
- [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue)
- [type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type)
- [header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header)
- [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter)
- [valueFormatter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter)
- ...etc

**Example**

```ts files=["columns-example.page.tsx","data.ts"]

```

### columns.className (`string | (param: InfiniteTableColumnStyleFnParams) => string`)

> Controls styling via CSS classes for the column. Can be a `string` or a function returning a `string` (a valid className).

If defined as a function, it accepts an object as a parameter (of type [`InfiniteTableColumnStylingFnParams`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnStylingFnParams)), which has the following properties:

- `column` - the current column where the className is being applied
- `data` - the data object for the current row. The type of this object is `DATA_TYPE | Partial<DATA_TYPE> | null`. For regular rows, it will be of type `DATA_TYPE`, while for group rows it will be `Partial<DATA_TYPE>`. For rows not yet loaded (because of batching being used), it will be `null`.
- `rowInfo` - the information about the current row - see [Using RowInfo](https://infinite-table.com/docs/learn/rows/using-row-info.md) for more details.
- `value` - the underlying value of the current cell - will generally be `data[column.field]`, if the column is bound to a `field` property
- ... and more, see [`InfiniteTableColumnStylingFnParams`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnStylingFnParams) for details

The `className` property can also be specified for [`columnTypes`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes)

**Example**

```ts files=["column-className-function-example.page.tsx","coloring.module.css"]

```

### components

> Components to override the default ones used by the DataGrid.

The following components can be overridden:

- `LoadMask` - see [`components.LoadMask`](https://infinite-table.com/docs/reference/infinite-table-props.md#components.LoadMask)
- `CheckBox`
- `Menu`
- `MenuIcon`

### components.LoadMask

> Allows customising the `LoadMask` displayed over the DataGrid when it's loading data.

To better test this out, you can use the controlled [`loading`](https://infinite-table.com/docs/reference/datasource-props/index.md#loading) prop on the `<DataSource />`

For more components that can be overriden, see [`components`](https://infinite-table.com/docs/reference/infinite-table-props.md#components)

**Example: Custom LoadMask component**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';

function LoadMask() {
  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        zIndex: 100,
        background: 'tomato',
        opacity: 0.3,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      <div
        style={{
          padding: 20,
          background: 'white',
          color: 'black',
          borderRadius: 5,
        }}
      >
        Loading App ...
      </div>
    </div>
  );
}

export default function App() {
  return (
    <DataSource<Employee> loading data={employees} primaryKey="id">
      <InfiniteTable<Employee>
        debugId="load-mask-example"
        components={{
          LoadMask,
        }}
        columnDefaultWidth={130}
        columns={columns}
      />
    </DataSource>
  );
}

type Employee = {
  id: string | number;
  name: string;
  salary: number;
  department: string;
  company: string;
};

const employees: Employee[] = [
  {
    id: 1,
    name: 'Bob',
    salary: 10_000,
    department: 'IT',
    company: 'Bobsons',
  },
  {
    id: 2,
    name: 'Alice',
    salary: 20_000,
    department: 'IT',
    company: 'Bobsons',
  },
];

const columns: Record<string, InfiniteTableColumn<Employee>> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 80,
  },
  name: {
    field: 'name',
  },
  salary: { field: 'salary', type: 'number' },
  department: { field: 'department', header: 'Dep.' },
  company: { field: 'company' },
};
```

### columns.renderTreeIcon (`boolean|(cellContext) => ReactNode`)

> Renders the tree expand/collapse icon in the column cells. If you want default behavior, specify `true` and the default icon will be used.

To render a custom icon, specify a function that returns a React node. The `cellContext` object param will contain all the information about the current cell.

The `cellContext` object contains a `toggleCurrentTreeNode` function property, which can be used to toggle the node state when clicked.

With the default value of `true`, an icon will be rendered only for parent nodes. If you want to render an icon for all nodes, specify a function (and differentiate between parent and leaf nodes), and it will be called regardless of whether the node is a parent or a leaf.

You can also use [`columns.renderTreeIconForParentNode`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderTreeIconForParentNode) to specify to customize the tree icon rendering for parent nodes or [`columns.renderTreeIconForLeafNode`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderTreeIconForLeafNode) to customize the tree icon rendering for leaf nodes.

**Example: Specifying a column to used as the tree icon**

```ts
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
} from '@infinite-table/infinite-react';
import { useMemo, useState } from 'react';

type FileSystemNode = {
  id: string;
  name: string;
  type: 'folder' | 'file';
  extension?: string;
  mimeType?: string;
  sizeInKB: number;
  children?: FileSystemNode[];
};

const allColumns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', header: 'Name' },
  type: { field: 'type', header: 'Type' },
  extension: { field: 'extension', header: 'Extension' },
  mimeType: { field: 'mimeType', header: 'Mime Type' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size (KB)' },
};

export default function App() {
  const [treeIcon, setTreeIcon] = useState<string>('name');

  const columns = useMemo(() => {
    const cols = { ...allColumns };

    cols[treeIcon] = {
      ...cols[treeIcon],
      renderTreeIcon: true,
    };

    return cols;
  }, [treeIcon]);
  return (
    <>
      <TreeDataSource nodesKey="children" primaryKey="id" data={dataSource}>
        <div
          style={{
            color: 'var(--infinite-cell-color)',
            padding: '10px',
          }}
        >
          <p>Select the tree column</p>
          <select
            value={treeIcon}
            onChange={(e) => setTreeIcon(e.target.value)}
            title="Select column to use for the tree icon"
          >
            {Object.keys(allColumns).map((key) => (
              <option value={key}>{key}</option>
            ))}
            <option value="none">No tree column</option>
          </select>
        </div>

        <TreeGrid columns={columns} />
      </TreeDataSource>
    </>
  );
}

const dataSource = () => {
  const nodes: FileSystemNode[] = [
    {
      id: '1',
      name: 'Documents',
      sizeInKB: 1200,
      type: 'folder',
      children: [
        {
          id: '10',
          name: 'Private',
          sizeInKB: 100,
          type: 'folder',
          children: [
            {
              id: '100',
              name: 'Report.docx',
              sizeInKB: 210,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '101',
              name: 'Vacation.docx',
              sizeInKB: 120,
              type: 'file',
              extension: 'docx',
              mimeType: 'application/msword',
            },
            {
              id: '102',
              name: 'CV.pdf',
              sizeInKB: 108,
              type: 'file',
              extension: 'pdf',
              mimeType: 'application/pdf',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      name: 'Desktop',
      sizeInKB: 1000,
      type: 'folder',
      children: [],
    },
    {
      id: '3',
      name: 'Media',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '30',
          name: 'Music',
          sizeInKB: 0,
          type: 'folder',
          children: [],
        },
        {
          id: '31',
          name: 'Videos',
          sizeInKB: 5400,
          type: 'folder',
          children: [
            {
              id: '310',
              name: 'Vacation.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};
```

**Example: Rendering a custom tree icon for both parent and leaf nodes**

This example renders a custom tree icon and uses the `toggleCurrentTreeNode` function to toggle the node state when Clicked. `toggleCurrentTreeNode` is a property of the `cellContext` argument passed to the `renderTreeIcon` function.

```tsx
import {
  InfiniteTableColumn,
  TreeDataSource,
  TreeGrid,
} from '@infinite-table/infinite-react';
import { CSSProperties } from 'react';

type FileSystemNode = {
  id: string;
  name: string;
  type: 'folder' | 'file';
  extension?: string;
  sizeInKB: number;
  children?: FileSystemNode[];
};

const renderTreeIcon: InfiniteTableColumn<FileSystemNode>['renderTreeIcon'] = ({
  rowInfo,
  toggleCurrentTreeNode,
}) => {
  return rowInfo.isParentNode ? (
    <FolderIcon open={rowInfo.nodeExpanded} onClick={toggleCurrentTreeNode} />
  ) : (
    <FileIcon />
  );
};

const svgStyle: CSSProperties = {
  verticalAlign: 'middle',
  position: 'relative',
  top: '-1px',
  marginInline: '5px',
};

const FileIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    height="20px"
    viewBox="0 -960 960 960"
    width="20px"
    fill="currentColor"
    style={svgStyle}
  >
    <path d="M240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z" />
  </svg>
);

const FolderIcon = ({
  onClick,
  open,
}: {
  onClick: () => void;
  open: boolean;
}) => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      height="20px"
      viewBox="0 -960 960 960"
      width="20px"
      fill="currentColor"
      onClick={onClick}
      style={{ ...svgStyle, cursor: 'pointer' }}
    >
      {open ? (
        <path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640H447l-80-80H160v480l96-320h684L837-217q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm0 0 72-240-72 240Zm-84-400v-80 80Z" />
      ) : (
        <path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H447l-80-80H160v480Zm0 0v-480 480Z" />
      )}
    </svg>
  );
};

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { renderTreeIcon, field: 'name', header: 'Name' },
  type: { field: 'type', header: 'Type' },
  extension: { field: 'extension', header: 'Extension' },
  size: { field: 'sizeInKB', type: 'number', header: 'Size (KB)' },
};

export default function App() {
  return (
    <TreeDataSource nodesKey="children" primaryKey="id" data={dataSource}>
      <TreeGrid columns={columns} />
    </TreeDataSource>
  );
}

const dataSource = () => {
  const nodes: FileSystemNode[] = [
    {
      id: '1',
      name: 'Documents',
      sizeInKB: 1200,
      type: 'folder',
      children: [
        {
          id: '10',
          name: 'Private',
          sizeInKB: 100,
          type: 'folder',
          children: [
            {
              id: '100',
              name: 'Report.docx',
              sizeInKB: 210,
              type: 'file',
              extension: 'docx',
            },
            {
              id: '101',
              name: 'Vacation.docx',
              sizeInKB: 120,
              type: 'file',
              extension: 'docx',
            },
            {
              id: '102',
              name: 'CV.pdf',
              sizeInKB: 108,
              type: 'file',
              extension: 'pdf',
            },
          ],
        },
      ],
    },
    {
      id: '2',
      name: 'Desktop',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
          extension: 'txt',
        },
      ],
    },
    {
      id: '3',
      name: 'Media',
      sizeInKB: 1000,
      type: 'folder',
      children: [
        {
          id: '30',
          name: 'Music - empty',
          sizeInKB: 0,
          type: 'folder',
          children: [],
        },
        {
          id: '31',
          name: 'Videos',
          sizeInKB: 5400,
          type: 'folder',
          children: [
            {
              id: '310',
              name: 'Vacation.mp4',
              sizeInKB: 108,
              type: 'file',
              extension: 'mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};
```

### columns.renderRowDetailIcon (`boolean|(cellContext) => ReactNode`)

> Renders the row detail expand/collapse icon in the column cell. Only used when [master-detail](https://infinite-table.com/docs/learn/master-detail/overview.md) is enabled.

If this function is a prop, it can be used to customize the icon rendered for expanding/collapsing the row detail.

See related [`rowDetailRenderer`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowDetailRenderer) for configuring master-detail.

**Example: Basic master detail DataGrid example**

This example shows a master DataGrid with the ID column configured to show the row detail expand icon.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
  InfiniteTableRowInfo,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  city: string;
  currency: string;
  country: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

type City = {
  id: number;
  name: string;
  country: string;
};

const masterColumns: InfiniteTablePropColumns<City> = {
  id: {
    field: 'id',
    header: 'ID',
    defaultWidth: 70,
    renderRowDetailIcon: true,
  },
  country: { field: 'country', header: 'Country' },
  city: { field: 'name', header: 'City', defaultFlex: 1 },
};

const detailColumns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  salary: {
    field: 'salary',
    type: 'number',
  },

  stack: { field: 'stack' },
  currency: { field: 'currency' },
  city: { field: 'city' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
const shouldReloadData = {
  sortInfo: true,
  filterValue: true,
};
function renderDetail(rowInfo: InfiniteTableRowInfo<City>) {
  console.log('rendering detail for master row', rowInfo.id);
  return (
    <DataSource<Developer>
      data={detailDataSource}
      primaryKey="id"
      shouldReloadData={shouldReloadData}
    >
      <InfiniteTable<Developer>
        debugId="master-detail-example"
        columnDefaultWidth={150}
        columnMinWidth={50}
        columns={detailColumns}
      />
    </DataSource>
  );
}

export default () => {
  return (
    <>
      <DataSource<City>
        data={citiesDataSource}
        primaryKey="id"
        defaultSortInfo={[
          {
            field: 'country',
            dir: 1,
          },
          {
            field: 'name',
            dir: 1,
          },
        ]}
      >
        <InfiniteTable<City>
          debugId="master-detail-example-2"
          domProps={domProps}
          columnDefaultWidth={150}
          columnMinWidth={50}
          columns={masterColumns}
          rowDetailRenderer={renderDetail}
        />
      </DataSource>
    </>
  );
};

// fetch an array of cities from the server
const citiesDataSource: DataSourceData<City> = () => {
  const cityNames = new Set<string>();
  const result: City[] = [];
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql`)
    .then((response) => response.json())
    .then((response) => {
      response.data.forEach((data: Developer) => {
        if (cityNames.has(data.city)) {
          return;
        }
        cityNames.add(data.city);
        result.push({
          name: data.city,
          country: data.country,
          id: result.length,
        });
      });

      return result;
    });
};

const detailDataSource: DataSourceData<Developer> = ({
  filterValue,
  sortInfo,
  masterRowInfo,
}) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }

  if (!filterValue) {
    filterValue = [];
  }
  if (masterRowInfo) {
    // filter by master country and city
    filterValue = [
      {
        field: 'city',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.name,
        },
      },
      {
        field: 'country',
        filter: {
          operator: 'eq',
          type: 'string',
          value: masterRowInfo.data.country,
        },
      },
      ...filterValue,
    ];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,

    filterValue
      ? 'filterBy=' +
        JSON.stringify(
          filterValue.map(({ field, filter }) => {
            return {
              field: field,
              operator: filter.operator,
              value:
                filter.type === 'number' ? Number(filter.value) : filter.value,
            };
          }),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### columns.components

> Specifies custom React components to use for column cells or header

The column components object can have either of the two following properties:

- [ColumnCell](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.ColumnCell) - a React component to use for rendering the column cells
- [HeaderCell](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.HeaderCell) - a React component to use for rendering the column header

- [Editor](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.Editor) - a React component to use for the editor, when editing is enabled for the column

- [FilterOperatorSwitch](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.FilterOperatorSwitch) - a React component to use for the filter operator switch - clicking the operator pops up a menu with the available operators for that column filter.

See [editing docs](https://infinite-table.com/docs/learn/editing/overview.md).

### columns.components.ColumnCell

> Specifies a custom React component to use for column cells

For column header see related [`columns.components.HeaderCell`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.HeaderCell).

Inside a component used as a cell, you have to use [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) to retrieve information about the currently rendered cell.

It's very important that you take

```tsx
const { domRef } = useInfiniteColumnCell<DATA_TYPE>();
```

the `domRef` from the [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) hook and pass it on to the root DOM element of your cell component.

```tsx
<div ref={domRef}>...</div>
```

**If you don't do this, the column rendering will not work.**

Also note that your React Component should be a functional component and have this signature

```tsx
function CustomComponent(props: React.HTMLProps<HTMLDivElement>) {
  return ...
}
```

that is, the `props` that the component is rendered with (is called with) are `HTMLProps` (more exactly `HTMLProps<HTMLDivElement>`) that you need to spread on the root DOM element of your component. If you want to customize anything, you can, for example, append a `className` or specify some extra styles.

In order to access the cell-related information, you don't use the props, but you call the [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) hook.

```tsx
const ExampleCellComponent: React.FunctionComponent<
  React.HTMLProps<HTMLDivElement>
> = (props) => {
  const { domRef } = useInfiniteColumnCell<Developer>();

  return (
    <div
      ref={domRef}
      {...props}
      className={`${props.className} extra-cls`}
      style={style}
    >
      {props.children} <div style={{ flex: 1 }} /> {emoji}
    </div>
  );
};
```

**Example: Custom components**

```tsx
import {
  InfiniteTable,
  DataSource,
  useInfiniteColumnCell,
  useInfiniteHeaderCell,
  InfiniteTablePropColumnTypes,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const DefaultHeaderComponent: React.FunctionComponent<
  React.HTMLProps<HTMLDivElement>
> = (props) => {
  const { column, domRef, columnSortInfo } = useInfiniteHeaderCell<Developer>();

  const style = {
    ...props.style,
    border: '1px solid #fefefe',
  };

  let sortTool = '';
  switch (columnSortInfo?.dir) {
    case undefined:
      sortTool = '👉';
      break;
    case 1:
      sortTool = '👇';
      break;
    case -1:
      sortTool = '☝🏽';
      break;
  }

  return (
    <div ref={domRef} {...props} style={style}>
      {/* here you would usually have: */}
      {/* {props.children} {sortTool} */}
      {/* but in this case we want to override the default sort tool as well (which is part of props.children) */}
      {column.field} {sortTool}
    </div>
  );
};

const StackComponent: React.FunctionComponent<
  React.HTMLProps<HTMLDivElement>
> = (props) => {
  const { value, domRef } = useInfiniteColumnCell<Developer>();

  const isFrontEnd = value === 'frontend';
  const emoji = isFrontEnd ? '⚛️' : '💽';
  const style = {
    padding: '5px 20px',
    border: `1px solid ${isFrontEnd ? 'red' : 'green'}`,
    ...props.style,
  };
  return (
    <div ref={domRef} {...props} style={style}>
      {props.children} <div style={{ flex: 1 }} /> {emoji}
    </div>
  );
};

const columnTypes: InfiniteTablePropColumnTypes<Developer> = {
  default: {
    // override all columns to use these components
    components: {
      HeaderCell: DefaultHeaderComponent,
    },
  },
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  stack: {
    field: 'stack',
    renderValue: ({ data }) => 'Stack: ' + data?.stack,
    components: {
      HeaderCell: DefaultHeaderComponent,
      ColumnCell: StackComponent,
    },
  },
  firstName: {
    field: 'firstName',
  },
  preferredLanguage: {
    field: 'preferredLanguage',
  },
};

export default function ColumnValueGetterExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="column-components-example"
          columns={columns}
          columnTypes={columnTypes}
        />
      </DataSource>
    </>
  );
}
```

### columns.components.Editor

> Specifies a custom React component to use for the editor, when [editing](https://infinite-table.com/docs/learn/editing/overview.md) is enabled for the column.

The editor component should use the [`useInfiniteColumnEditor`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnEditor) hook to have access to cell-related information and to confirm, cancel or reject the edit.

Here's the implementation for our default editor

```tsx
export function InfiniteTableColumnEditor<T>() {
  const { initialValue, setValue, confirmEdit, cancelEdit, readOnly } =
    useInfiniteColumnEditor<T>();

  const domRef = useRef<HTMLInputElement>();
  const refCallback = React.useCallback((node: HTMLInputElement) => {
    domRef.current = node;

    if (node) {
      node.focus();
    }
  }, []);

  const onKeyDown = useCallback((event: React.KeyboardEvent) => {
    const { key } = event;
    if (key === 'Enter' || key === 'Tab') {
      confirmEdit();
    } else if (key === 'Escape') {
      cancelEdit();
    } else {
      event.stopPropagation();
    }
  }, []);

  return (
    <>
      <input
        readOnly={readOnly}
        ref={refCallback}
        onKeyDown={onKeyDown}
        onBlur={() => confirmEdit()}
        className={'...'}
        type={'text'}
        defaultValue={initialValue}
        onChange={useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
          setValue(event.target.value);
        }, [])}
      />
    </>
  );
}
```

**Example: Column with custom editor**

Try editing the `salary` column - it has a custom editor

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
  useInfiniteColumnEditor,
} from '@infinite-table/infinite-react';
import { useRef, useCallback } from 'react';

type Developer = {
  id: number;
  firstName: string;
  currency: string;
  stack: string;
  hobby: string;
  salary: string;
};
const dataSource: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    currency: 'USD',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'USD 1000',
  },
  {
    id: 2,
    firstName: 'Jane',
    currency: 'EUR',
    stack: 'backend',
    hobby: 'reading',
    salary: 'EUR 2000',
  },
  {
    id: 3,
    firstName: 'Jack',
    currency: 'GBP',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'GBP 3000',
  },
  {
    id: 4,
    firstName: 'Jill',
    currency: 'USD',
    stack: 'backend',
    hobby: 'reading',
    salary: 'USD 4000',
  },
];

const CustomEditor = () => {
  const { initialValue, confirmEdit, cancelEdit } = useInfiniteColumnEditor();

  const domRef = useRef<HTMLInputElement>(null);

  const onKeyDown = useCallback((event: React.KeyboardEvent) => {
    const { key } = event;
    if (key === 'Enter' || key === 'Tab') {
      confirmEdit(domRef.current?.value);
    } else if (key === 'Escape') {
      cancelEdit();
    } else {
      event.stopPropagation();
    }
  }, []);

  return (
    <div
      style={{
        background: '#ad1',
        padding: 5,
        width: '100%',
        height: '100%',
        position: 'absolute',
        left: 0,
        top: 0,
      }}
    >
      <input
        style={{ width: '100%', height: '100%' }}
        autoFocus
        ref={domRef}
        defaultValue={initialValue}
        onKeyDown={onKeyDown}
      />
    </div>
  );
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80, defaultEditable: false },

  salary: {
    components: {
      // reference to the custom editor component
      Editor: CustomEditor,
    },

    defaultWidth: 320,
    field: 'salary',
    header: 'Salary - edit accepts numbers only',
    style: { color: 'tomato' },
    getValueToEdit: ({ value }) => {
      return parseInt(value.substr(4), 10);
    },
    getValueToPersist: ({ value, data }) => {
      return `${data!.currency} ${parseInt(value, 10)}`;
    },
    shouldAcceptEdit: ({ value }) => {
      return parseInt(value, 10) == value;
    },
  },

  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  currency: {
    field: 'currency',
    header: 'Currency',
  },
};

export default function InlineEditingExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="custom-editor-hooks-example"
          columns={columns}
          columnDefaultEditable
        />
      </DataSource>
    </>
  );
}
```

### columns.components.HeaderCell

> Specifies a custom React component to use for column headers

For column cells see related [`columns.components.ColumnCell`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.ColumnCell).

Inside a custom component used as a column header, you have to use [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) to retrieve information about the currently rendered header cell.

It's very important that you take

```tsx
const { domRef } = useInfiniteHeaderCell<DATA_TYPE>();
```

the `domRef` from the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) hook and pass it on to the root DOM element of your header component.

```tsx
<div ref={domRef}>...</div>
```

**If you don't do this, the column header rendering will not work.**

Also note that your React Component should be a functional component and have this signature

```tsx
function CustomHeaderComponent(props: React.HTMLProps<HTMLDivElement>) {
  return ...
}
```

that is, the `props` that the component is rendered with (is called with) are `HTMLProps` (more exactly `HTMLProps<HTMLDivElement>`) that you need to spread on the root DOM element of your component. If you want to customize anything, you can, for example, append a `className` or specify some extra styles.

In order to access the column header-related information, you don't use the props, but you call the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) hook.

```tsx
const ExampleHeaderComponent: React.FunctionComponent<
  React.HTMLProps<HTMLDivElement>
> = (props) => {
  const { domRef } = useInfiniteHeaderCell<Developer>();

  return (
    <div
      ref={domRef}
      {...props}
      className={`${props.className} extra-cls`}
      style={style}
    >
      {props.children} <div style={{ flex: 1 }} /> {emoji}
    </div>
  );
};
```

**Example: Custom components**

```tsx
import {
  InfiniteTable,
  DataSource,
  useInfiniteColumnCell,
  useInfiniteHeaderCell,
  InfiniteTablePropColumnTypes,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const DefaultHeaderComponent: React.FunctionComponent<
  React.HTMLProps<HTMLDivElement>
> = (props) => {
  const { column, domRef, columnSortInfo } = useInfiniteHeaderCell<Developer>();

  const style = {
    ...props.style,
    border: '1px solid #fefefe',
  };

  let sortTool = '';
  switch (columnSortInfo?.dir) {
    case undefined:
      sortTool = '👉';
      break;
    case 1:
      sortTool = '👇';
      break;
    case -1:
      sortTool = '☝🏽';
      break;
  }

  return (
    <div ref={domRef} {...props} style={style}>
      {/* here you would usually have: */}
      {/* {props.children} {sortTool} */}
      {/* but in this case we want to override the default sort tool as well (which is part of props.children) */}
      {column.field} {sortTool}
    </div>
  );
};

const StackComponent: React.FunctionComponent<
  React.HTMLProps<HTMLDivElement>
> = (props) => {
  const { value, domRef } = useInfiniteColumnCell<Developer>();

  const isFrontEnd = value === 'frontend';
  const emoji = isFrontEnd ? '⚛️' : '💽';
  const style = {
    padding: '5px 20px',
    border: `1px solid ${isFrontEnd ? 'red' : 'green'}`,
    ...props.style,
  };
  return (
    <div ref={domRef} {...props} style={style}>
      {props.children} <div style={{ flex: 1 }} /> {emoji}
    </div>
  );
};

const columnTypes: InfiniteTablePropColumnTypes<Developer> = {
  default: {
    // override all columns to use these components
    components: {
      HeaderCell: DefaultHeaderComponent,
    },
  },
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  stack: {
    field: 'stack',
    renderValue: ({ data }) => 'Stack: ' + data?.stack,
    components: {
      HeaderCell: DefaultHeaderComponent,
      ColumnCell: StackComponent,
    },
  },
  firstName: {
    field: 'firstName',
  },
  preferredLanguage: {
    field: 'preferredLanguage',
  },
};

export default function ColumnValueGetterExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="column-components-example"
          columns={columns}
          columnTypes={columnTypes}
        />
      </DataSource>
    </>
  );
}
```

### columns.contentFocusable (`boolean|(params) => boolean`)

> Specifies if the column (or cell, if a function is used) renders content that will/should be focusable (via tab-navigation)

**Example: Columns with cell content focusable**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const inputStyle = {
  background: 'white',
  color: 'black',
  padding: '2px 10px',
};
const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  stack: {
    field: 'stack',
    // this makes the column content focusable
    contentFocusable: true,
    renderValue: ({ value }) => (
      <input type="text" value={value} style={inputStyle} />
    ),
  },
  firstName: {
    field: 'firstName',
  },
  preferredLanguage: {
    field: 'preferredLanguage',
    // this makes the column content focusable
    contentFocusable: true,
    renderValue: ({ value }) => (
      <input type="text" value={value} style={inputStyle} />
    ),
  },
};

export default function ColumnValueGetterExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="column-contentFocusable-example"
          columns={columns}
        />
      </DataSource>
    </>
  );
}
```

### columns.cssEllipsis (`boolean`)

> Specifies if the column should show ellipsis for content that is too long and does not fit the column width.

For header ellipsis, see related [`headerCssEllipsis`](https://infinite-table.com/docs/reference/infinite-table-props.md#headerCssEllipsis).

**Example: First name column(first) has cssEllipsis set to false**

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    cssEllipsis: false,
    defaultWidth: 60,
  },
  preferredLanguage: {
    field: 'preferredLanguage',
    defaultWidth: 100,
    headerCssEllipsis: false,
    cssEllipsis: true,
  },
  salary: {
    field: 'salary',
    type: 'number',
  },
  stack: { field: 'stack' },
  country: { field: 'country' },
  age: { field: 'age', type: 'number' },
  currency: { field: 'currency', type: 'number' },
};

export default function CssEllipsis() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="columns-cssEllipsis-example"
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

### columns.dataType (`string`)

> Specifies the type of the data for the column. For now, it's better to simply use [`columns.type`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type).

If a column doesn't specify a [sortType](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.sortType), the `dataType` will be used instead to determine the type of sorting to use. If neither `sortType` nor `dataType` are specified, the [column.type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) will be used.

### columns.defaultDraggable (`boolean`)

> Specifies whether the column is draggable by default (for reordering columns).

This property overrides the global [`columnDefaultDraggable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultDraggable).

### draggableColumns (`boolean`)

> Specifies whether columns are draggable (for reordering columns).

This property overrides the global [`columnDefaultDraggable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultDraggable) and the column-level [`columns.defaultDraggable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultDraggable).

### columnDefaultDraggable (`boolean`)

> Specifies whether columns are draggable by default (for reordering columns).

This is overriden by [`columns.defaultDraggable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultDraggable) and [`draggableColumns`](https://infinite-table.com/docs/reference/infinite-table-props.md#draggableColumns).

### columns.defaultEditable (`boolean|(param)=>boolean|Promise<boolean>`)

> Controls if the column is editable or not.

This overrides the global [`columnDefaultEditable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultEditable).
This is overridden by the [`editable`](https://infinite-table.com/docs/reference/infinite-table-props.md#editable) prop.

The value for this property can be either a `boolean` or a function.

If it is a function, it will be called when an edit is triggered on the column. The function will be called with a single object that contains the following properties:

- `value` - the current value of the cell (the value currently displayed, so after [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) and [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) have been applied)
- `rawValue` - the current value of the cell, but before any formatting and custom rendering has been applied. This is either the field value from the current data object, or the result of the column [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) function.
- `data` - the data object (of type `DATA_TYPE`) for the current row
- `rowInfo` - the row info object that underlies the row
- `column` - the current column on which editing is invoked
- `api` - a reference to the [InfiniteTable API](https://infinite-table.com/docs/reference/api/index.md)
- `dataSourceApi` - - a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)

The function can return a `boolean` value or a `Promise` that resolves to a `boolean` - this means you can asynchronously decide whether the cell is editable or not.

Making [column.defaultEditable](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultEditable) a function gives you the ability to granularly control which cells are editable or not (even within the same column, based on the cell value or other values you have access to).

By default, double-clicking an editable cell will show the cell editor. You can prevent this by returning `{preventEdit: true}` from the [onCellDoubleClick](https://infinite-table.com/docs/reference/infinite-table-props.md#onCellDoubleClick) function prop.

**Example**

Only the `salary` column is editable.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  currency: string;
  stack: string;
  hobby: string;
  salary: string;
};
const dataSource: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    currency: 'USD',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'USD 1000',
  },
  {
    id: 2,
    firstName: 'Jane',
    currency: 'EUR',
    stack: 'backend',
    hobby: 'reading',
    salary: 'EUR 2000',
  },
  {
    id: 3,
    firstName: 'Jack',
    currency: 'GBP',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'GBP 3000',
  },
  {
    id: 4,
    firstName: 'Jill',
    currency: 'USD',
    stack: 'backend',
    hobby: 'reading',
    salary: 'USD 4000',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },

  salary: {
    // the only editable column
    defaultEditable: true,

    defaultWidth: 320,
    field: 'salary',
    header: 'Salary - edit accepts numbers only',
    style: { color: 'tomato' },
    getValueToEdit: ({ value }) => {
      return parseInt(value.substr(4), 10);
    },
    getValueToPersist: ({ value, data }) => {
      return `${data!.currency} ${parseInt(value, 10)}`;
    },
  },

  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  currency: {
    field: 'currency',
    header: 'Currency',
  },
};

export default function InlineEditingExample() {
  const shouldAcceptEdit = ({ value }: { value: any }) => {
    return parseInt(value, 10) == value;
  };

  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="global-should-accept-edit-example"
          columns={columns}
          columnDefaultEditable={false}
          shouldAcceptEdit={shouldAcceptEdit}
        />
      </DataSource>
    </>
  );
}
```

### columns.defaultFlex (`number`)

> Specifies a default flex for the column

If you want more control on sizing, use controlled [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) (or uncontrolled [`defaultColumnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing)).

See related [`columns.defaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth)

### columnGroupVisibility (`Record<string, boolean>`)

> Controls the visibility of column groups. By default, column groups are visible.

```tsx
<InfiniteTable<DATA_TYPE>
  columnGroupVisibility={{
    'country': false,
    'city': true,
  }}
  columns={{...}}
/>
```

### columns.defaultHiddenWhenGroupedBy (`'*'| true | keyof DATA_TYPE | { [keyof DATA_TYPE]: true }`)

> Controls default column visibility when [`groupBy`](https://infinite-table.com/docs/reference/datasource-props/index.md#groupBy) is used.

This property does not apply (work) when controlled [`columnVisibility`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnVisibility) is used, it only works with uncontrolled column visibility.

The value for this property can be one of the following:

- the `'*'` string - this means, the column is hidden whenever there are groups - so any groups.
- a `string`, namely a field from the bound type of the `DataSource` (so type is `keyof DATA_TYPE`) - the column is hidden whenever there is grouping that includes the specified field. The grouping can contain any other fields, but if it includes the specified field, the column is hidden.
- `true` - the column is hidden when there grouping that uses the field that the column is bound to.
- `an object with keys` of type `keyof DATA_TYPE` and values being `true` - whenever the grouping includes any of the fields that are in the keys of this object, the column is hidden.

**Example**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';

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 columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id' },
  firstName: { field: 'firstName' },
  preferredLanguage: {
    field: 'preferredLanguage',
    // hide whenever grouped by preferredLanguage
    defaultHiddenWhenGroupedBy: 'preferredLanguage',
  },
  stack: { field: 'stack' },
  country: { field: 'country' },
  canDesign: { field: 'canDesign' },
  hobby: { field: 'hobby' },

  city: {
    field: 'city',
    // hide whenever grouped by country or city
    defaultHiddenWhenGroupedBy: {
      country: true,
      city: true,
    },
  },
  age: { field: 'age' },
  salary: {
    field: 'salary',
    type: 'number',
    // hide whenever there is grouping
    defaultHiddenWhenGroupedBy: '*',
  },
  currency: { field: 'currency' },
};

const columnSizing: InfiniteTablePropColumnSizing = {
  country: { width: 100 },
  city: { flex: 1, minWidth: 100 },
  salary: { flex: 2, maxWidth: 500 },
};

export default function App() {
  return (
    <DataSource<Developer>
      data={dataSource}
      defaultGroupBy={[
        { field: 'stack' },
        { field: 'preferredLanguage' },
        { field: 'country' },
      ]}
      primaryKey="id"
    >
      <InfiniteTable<Developer>
        debugId="columnDefaultHiddenWhenGroupedBy-example"
        columns={columns}
        columnDefaultWidth={250}
        columnSizing={columnSizing}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### columns.defaultWidth (`number`)

> Specifies a default width for the column

If you want more control on sizing, use controlled [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) (or uncontrolled [`defaultColumnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing)).

See related [`columns.defaultFlex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex)

### columns.field (`keyof DATA_TYPE`)

> Binds the column to the specified data field. It should be a keyof `DATA_TYPE`.

It can be the same or different to the column id. This is not used for referencing the column in various other props - the column key (column id) is used for that.

If no [`columns.header`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) is specified, it will be used as the column header.

**Example**

```ts files=["columns-example.page.tsx","data.ts"]

```

Group columns can also be bound to a field, like in the snippet below.

**Example**

In this example, the group column is bound to the `firstName` field, so this field will be rendered in non-group rows for this column.

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
  InfiniteTableProps,
  InfiniteTablePropColumns,
  DataSourceProps,
} from '@infinite-table/infinite-react';
import * as React from 'react';

const columns: InfiniteTablePropColumns<Developer> = {
  country: {
    field: 'country',
  },
  firstName: {
    field: 'firstName',
    defaultHiddenWhenGroupedBy: '*',
  },
  stack: {
    field: 'stack',
  },
  age: { field: 'age' },
  id: { field: 'id' },
  preferredLanguage: {
    field: 'preferredLanguage',
  },
  canDesign: {
    field: 'canDesign',
    renderGroupValue: ({ value }) => `Can design: ${value || ''}`,
  },
};

const defaultGroupBy: DataSourceProps<Developer>['groupBy'] = [
  {
    field: 'canDesign',
  },
  {
    field: 'stack',
  },
  {
    field: 'preferredLanguage',
  },
];

const groupColumn: InfiniteTableProps<Developer>['groupColumn'] = {
  field: 'firstName',
  renderSelectionCheckBox: true,
  defaultWidth: 300,
};

const domProps = {
  style: {
    flex: 1,
    minHeight: 500,
  },
};

export default function App() {
  return (
    <DataSource<Developer>
      data={dataSource}
      groupBy={defaultGroupBy}
      selectionMode="multi-row"
      primaryKey="id"
    >
      <InfiniteTable<Developer>
        debugId="group-column-bound-to-field-example"
        columns={columns}
        domProps={domProps}
        groupColumn={groupColumn}
        columnDefaultWidth={150}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

### columns.filterType (`string`)

> Use this to configure the filter type for the column, when the `filterType` needs to be different from the column [type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type).

See related [`columns.type`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type)

If the type of filter you want to show does not match the column [type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type), you can specify the filter with the [column.filterType](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.filterType) property. Only use this when the type of the data differs from the type of the filter (eg: you have a numeric column, with a custom filter type).

**Example: Custom column filterType for the salary column**

In this example, the `salary` column has `type="number"` and `filterType="salary"`.

This means the sort order defined for `type="number"` will be used while displaying a custom type of filter.

```ts
import * as React from 'react';

import {
  DataSourceData,
  DataSource,
  InfiniteTable,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

const data: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
  },
  salary: {
    defaultFilterable: true,
    field: 'salary',
    type: 'number',
    filterType: 'salary',
  },

  firstName: {
    field: 'firstName',
  },
  stack: { field: 'stack' },
  currency: { field: 'currency', defaultFilterable: false },
};

function getIcon(icon: string) {
  return () => (
    <div
      style={{
        width: 20,
        display: 'flex',
        justifyContent: 'center',
        flexFlow: 'row',
      }}
    >
      {icon}
    </div>
  );
}

const domProps = {
  style: {
    height: '100%',
  },
};
export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          defaultFilterValue={[]}
          filterDelay={0}
          filterMode="local"
          filterTypes={{
            salary: {
              defaultOperator: 'gt',
              emptyValues: ['', null, undefined],
              operators: [
                {
                  name: 'gt',
                  label: 'Salary Greater Than',
                  components: {
                    Icon: getIcon('>'),
                  },
                  fn: ({ currentValue, filterValue }) => {
                    return currentValue > filterValue;
                  },
                },
                {
                  name: 'gte',
                  components: {
                    Icon: getIcon('>='),
                  },
                  label: 'Salary Greater Than or Equal',
                  fn: ({ currentValue, filterValue }) => {
                    return currentValue >= filterValue;
                  },
                },
                {
                  name: 'lt',
                  components: {
                    Icon: getIcon('<'),
                  },
                  label: 'Salary Less Than',
                  fn: ({ currentValue, filterValue }) => {
                    return currentValue < filterValue;
                  },
                },
                {
                  name: 'lte',
                  components: {
                    Icon: getIcon('<='),
                  },
                  label: 'Salary Less Than or Equal',
                  fn: ({ currentValue, filterValue }) => {
                    return currentValue <= filterValue;
                  },
                },
              ],
            },
          }}
        >
          <InfiniteTable<Developer>
            debugId="column-filterType-example"
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

### columns.getValueToEdit (`(params) => any|Promise<any>`)

> Allows customizing the value that will be passed to the cell editor when it is displayed (when editing starts).

The function is called with an object that has the following properties:

- `value` - the value of the cell (the value that is displayed in the cell before editing starts). This is the value resulting after [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) and [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) have been applied)
- `rawValue` - the raw value of the cell, before any formatting and custom rendering has been applied. This is either the field value from the current data object, or the result of the column [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) function.
- `data` - the current data object
- `rowInfo` - the row info object that underlies the row
- `column` - the current column on which editing is invoked
- `api` - a reference to the [InfiniteTable API](https://infinite-table.com/docs/reference/api/index.md)
- `dataSourceApi` - - a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)

This function can be async. Return a `Promise` to wait for the value to be resolved and then passed to the cell editor.

See related [`columns.getValueToPersist`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.getValueToPersist) and [`columns.shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit).

**Example**

In this example, the `salary` for each row includes the currency string.

<p>When editing starts, we want to remove the currency string and only show the numeric value in the editor - we do this via [`columns.getValueToEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.getValueToEdit).</p>

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  currency: string;
  stack: string;
  hobby: string;
  salary: string;
};
const dataSource: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    currency: 'USD',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'USD 1000',
  },
  {
    id: 2,
    firstName: 'Jane',
    currency: 'EUR',
    stack: 'backend',
    hobby: 'reading',
    salary: 'EUR 2000',
  },
  {
    id: 3,
    firstName: 'Jack',
    currency: 'GBP',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'GBP 3000',
  },
  {
    id: 4,
    firstName: 'Jill',
    currency: 'USD',
    stack: 'backend',
    hobby: 'reading',
    salary: 'USD 4000',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80, defaultEditable: false },

  salary: {
    defaultWidth: 320,
    field: 'salary',
    header: 'Salary - edit accepts numbers only',
    style: { color: 'tomato' },
    getValueToEdit: ({ value }) => {
      return parseInt(value.substr(4), 10);
    },
    getValueToPersist: ({ value, data }) => {
      return `${data!.currency} ${parseInt(value, 10)}`;
    },
    shouldAcceptEdit: ({ value }) => {
      return parseInt(value, 10) == value;
    },
  },

  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  currency: {
    field: 'currency',
    header: 'Currency',
  },
};

export default function InlineEditingExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="inline-editing-custom-edit-value-example"
          columns={columns}
          columnDefaultEditable
        />
      </DataSource>
    </>
  );
}
```

### columns.getValueToPersist (`(params) => any|Promise<any>`)

> Allows customizing the value that will be persisted when an edit has been accepted.

The function is called with an object that has the following properties:

- `initialValue` - the initial value of the cell (the value that was displayed in the cell before editing started). This is the value resulting after [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) and [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) have been applied)
- `value` - the current value that was accepted as an edit and which came from the cell editor.
- `rawValue` - the raw value of the cell, before any formatting and custom rendering has been applied. This is either the field value from the current data object, or the result of the column [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) function.
- `data` - the current data object
- `rowInfo` - the row info object that underlies the row
- `column` - the current column on which editing is invoked
- `api` - a reference to the [InfiniteTable API](https://infinite-table.com/docs/reference/api/index.md)
- `dataSourceApi` - - a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)

This function can be async. Return a `Promise` to wait for the value to be resolved and then persisted.

See related [`columns.getValueToEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.getValueToEdit) and [`columns.shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit).

**Example**

In this example, the `salary` for each row includes the currency string.

<p>When an edit is accepted, we want the persisted value to include the currency string as well (like the original value did) - we do this via [`columns.getValueToPersist`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.getValueToPersist).</p>

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  currency: string;
  stack: string;
  hobby: string;
  salary: string;
};
const dataSource: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    currency: 'USD',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'USD 1000',
  },
  {
    id: 2,
    firstName: 'Jane',
    currency: 'EUR',
    stack: 'backend',
    hobby: 'reading',
    salary: 'EUR 2000',
  },
  {
    id: 3,
    firstName: 'Jack',
    currency: 'GBP',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'GBP 3000',
  },
  {
    id: 4,
    firstName: 'Jill',
    currency: 'USD',
    stack: 'backend',
    hobby: 'reading',
    salary: 'USD 4000',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80, defaultEditable: false },

  salary: {
    defaultWidth: 320,
    field: 'salary',
    header: 'Salary - edit accepts numbers only',
    style: { color: 'tomato' },
    getValueToEdit: ({ value }) => {
      return parseInt(value.substr(4), 10);
    },
    getValueToPersist: ({ value, data }) => {
      return `${data!.currency} ${parseInt(value, 10)}`;
    },
    shouldAcceptEdit: ({ value }) => {
      return parseInt(value, 10) == value;
    },
  },

  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  currency: {
    field: 'currency',
    header: 'Currency',
  },
};

export default function InlineEditingExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="inline-editing-custom-edit-value-example"
          columns={columns}
          columnDefaultEditable
        />
      </DataSource>
    </>
  );
}
```

### columns.renderHeader (`(param: InfiniteTableColumnHeaderParam) => ReactNode`)

> A custom rendering function for the column header. Called with an object of type [`InfiniteTableColumnHeaderParam`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnHeaderParam).

It's the equivalent of [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) but for the [column.header](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header).

It gives you access to the column, along with information about sorting, filtering, grouping, etc.

It is called with a single argument, of type [`InfiniteTableColumnHeaderParam`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnHeaderParam).

### columns.header (`React.ReactNode|({column, columnSortInfo, columnApi})=>React.ReactNode`)

> Specifies the column header. Can be a static value or a function that returns a React node.

If no `header` is specified for a column, the [field](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.field) will be used instead.

If a function is provided, it will be called with an argument with the following properties:

- `column`
- `columnSortInfo` - will allow you to render custom header based on the sort state of the column.
- `columnApi` - [API](reference/column-api) for the current column. Can be useful if you customize the header and want to programatically trigger actions like sorting, show/hide column menu, etc.

When we implement filtering, you'll also have access to the column filter.

For styling the column header, you can use [headerStyle](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerStyle) or [headerClassName](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerClassName).

For configuring the column header height, see the [`columnHeaderHeight`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnHeaderHeight) prop.

**Example**

```ts files=["columns-header-example.page.tsx","data.ts"]

```

In the `column.header` function you can use hooks or [render custom React components via column.components.HeaderCell](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.HeaderCell). To make it easier to access the param of the `header` function, we've exposed the [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell) - use it to gain access to the same object that is passed as an argument to the `header` function.

**Example: Column with custom header that uses useInfiniteHeaderCell**

```ts
import {
  InfiniteTable,
  DataSource,
  useInfiniteHeaderCell,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const HobbyHeader: React.FC = function () {
  const { column } = useInfiniteHeaderCell<Developer>();

  return <b style={{ color: '#0000c2' }}>{column?.field} 🤷📸👨🏻‍🍳💃📚⛹️</b>;
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', maxWidth: 80 },
  stack: {
    field: 'stack',
  },
  hobby: {
    field: 'hobby',
    components: {
      HeaderCell: HobbyHeader,
    },
  },
};

export default function ColumnHeaderExampleWithHooks() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="column-header-hooks-example"
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

**Example: Custom header with button to trigger the column context menu**

The `preferredLanguage` column has a custom header that shows a button for triggering the column context menu.

In addition, the currency and preferredLanguage columns have a custom context menu icon.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  currency: {
    field: 'currency',

    // custom menu icon
    renderMenuIcon: () => <div>🌎</div>,
  },

  preferredLanguage: {
    field: 'preferredLanguage',
    defaultWidth: 350,
    header: ({ columnApi, renderLocation }) => {
      // if we're inside the column menu with all columns, return only the col name
      if (renderLocation === 'column-menu') {
        return 'Preferred Language';
      }

      // but for the real column header
      // return this custom content
      return (
        <>
          Preferred Language{' '}
          <button
            // we need to stop propagation so we don't trigger a sort when this button is clicked
            onPointerDown={(e) => e.stopPropagation()}
            onMouseDown={(e) => {
              // again, stop propagation so the menu is not closed automatically
              // so we can control it in the line below
              e.stopPropagation();

              columnApi.toggleContextMenu(e.target);
            }}
            style={{ border: '1px solid magenta', margin: 2 }}
          >
            Toggle menu
          </button>
        </>
      );
    },

    // custom menu icon
    renderMenuIcon: () => <div>🌎</div>,
  },
  salary: {
    field: 'salary',
    // hide the menu icon
    renderMenuIcon: false,
  },
  country: {
    field: 'country',
  },
  id: { field: 'id', defaultWidth: 80, renderMenuIcon: false },
  firstName: {
    field: 'firstName',
  },
};

export default function ColumnContextMenuItems() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="getColumnMenuItems-example"
          columnHeaderHeight={70}
          columns={columns}
          getColumnMenuItems={(items, { column }) => {
            if (column.id === 'firstName') {
              // you can adjust the default items for a specific column
              items.splice(0, 0, {
                key: 'firstName',
                label: 'First name menu item',
                onAction: () => {
                  console.log('Hey there!');
                },
              });
            }

            items.push(
              {
                key: 'hello',
                label: 'Hello World',
                onAction: () => {
                  alert('Hello World from column ' + column.id);
                },
              },
              {
                key: 'translate',
                label: 'Translate',
                menu: {
                  items: [
                    {
                      key: 'translateToEnglish',
                      label: 'English',
                      onAction: () => {
                        console.log('Translate to English');
                      },
                    },
                    {
                      key: 'translateToFrench',
                      label: 'French',
                      onAction: () => {
                        console.log('Translate to French');
                      },
                    },
                  ],
                },
              },
            );
            return items;
          }}
        />
      </DataSource>
    </>
  );
}
```

### columns.headerClassName (`string | (args) => string`)

> Controls the css class name for the column header. Can be a string or a function returning a string.

If defined as a function, it accepts an object as a parameter, which has the following properties:

- `column` - the current column where the style is being applied
- `columnSortInfo` - the sorting information for the column
- `columnFilterValue` - the filtering information for the column
- `dragging` - whether the current column is being dragged at the current time (during a column reorder)

The `headerClassName` property can also be specified for [columnTypes](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.headerClassName).

For styling with inline styles, see [`columns.headerStyle`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerStyle).

### columns.headerCssEllipsis (`boolean`)

> Specifies if the column should show ellipsis in the column header if the header is too long and does not fit the column width.

If this property is not specified, the value of [`columns.cssEllipsis`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.cssEllipsis) will be used.

For normal cell ellipsis, see related [`cssEllipsis`](https://infinite-table.com/docs/reference/infinite-table-props.md#cssEllipsis).

**Example: Preferred Language column(second) has headerCssEllipsis set to false**

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    cssEllipsis: false,
    defaultWidth: 60,
  },
  preferredLanguage: {
    field: 'preferredLanguage',
    defaultWidth: 100,
    headerCssEllipsis: false,
    cssEllipsis: true,
  },
  salary: {
    field: 'salary',
    type: 'number',
  },
  stack: { field: 'stack' },
  country: { field: 'country' },
  age: { field: 'age', type: 'number' },
  currency: { field: 'currency', type: 'number' },
};

export default function CssEllipsis() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="columns-cssEllipsis-example"
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

### columns.headerStyle (`CSSProperties | (args) => CSSProperties`)

> Controls styling for the column header. Can be a style object or a function returning a style object.

If defined as a function, it accepts an object as a parameter, which has the following properties:

- `column` - the current column where the style is being applied
- `columnSortInfo` - the sorting information for the column
- `columnFilterValue` - the filtering information for the column
- `dragging` - whether the current column is being dragged at the current time (during a column reorder)

The `headerStyle` property can also be specified for [columnTypes](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.headerStyle).

For styling with CSS, see [`columns.headerClassName`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerClassName).

For configuring the column header height, see the [`columnHeaderHeight`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnHeaderHeight) prop.

### columns.maxWidth (`number`)

> Configures the maximum width for the column.

If not specified, [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth) will be used (defaults to `2000`).

### columns.minWidth (`number`)

> Configures the minimum width for the column.

If not specified, [`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth) will be used (defaults to `30`).

### columns.render (`(cellContext) => Renderable`)

> Customizes the rendering of the column. The argument passed to the function is an object of type [`InfiniteTableColumnCellContextType`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnCellContextType)

See related [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue), [`columns.renderGroupValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupValue)

The difference between [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) and [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) is only for special columns (for now, only group columns are special columns, but more will come) when `InfiniteTable` renders additional content inside the column (eg: collapse/expand tool for group rows). The [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) function allows you to override the additional content. So if you specify this function, it's up to you to render whatever content, including the collapse/expand tool.

Note that for customizing the collapse/expand tool, you can use specify `renderGroupIcon` function on the group column.

To understand how the rendering pipeline works, head over to the page on [Column rendering](https://infinite-table.com/docs/learn/columns/column-rendering.md#rendering-pipeline).

The [render](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) and [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) functions are called with an object that has the following properties:

- data - the data object (of type `DATA_TYPE | Partial<DATA_TYPE> | null`) for the row.
- rowInfo - very useful information about the current row. See [Using RowInfo](https://infinite-table.com/docs/learn/rows/using-row-info.md) for more details.
- renderBag - read more about this in the docs for [Column rendering pipeline](https://infinite-table.com/docs/learn/columns/column-rendering.md#rendering-pipeline)

**Example: Column with custom render**

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceGroupBy,
  InfiniteTablePropGroupColumn,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  stack: {
    field: 'stack',
  },
  firstName: {
    field: 'firstName',
  },
  preferredLanguage: { field: 'preferredLanguage' },
};

const defaultGroupBy: DataSourceGroupBy<Developer>[] = [{ field: 'stack' }];

const groupColumn: InfiniteTablePropGroupColumn<Developer> = {
  defaultWidth: 250,
  render: ({ rowInfo, toggleCurrentGroupRow }) => {
    if (rowInfo.isGroupRow) {
      const { collapsed } = rowInfo;
      const expandIcon = (
        <svg
          style={{
            display: 'inline-block',
            fill: collapsed ? '#b00000' : 'blue',
          }}
          width="20px"
          height="20px"
          viewBox="0 0 24 24"
          fill="#000000"
        >
          {collapsed ? (
            <>
              <path d="M0 0h24v24H0V0z" fill="none" />
              <path d="M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z" />
            </>
          ) : (
            <path d="M7.41 18.59L8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z" />
          )}
        </svg>
      );
      return (
        <div
          style={{
            cursor: 'pointer',
            display: 'flex',
            alignItems: 'center',
            color: collapsed ? '#b00000' : 'blue',
          }}
          onClick={() => toggleCurrentGroupRow()}
        >
          <i style={{ marginRight: 5 }}>Grouped by</i> <b>{rowInfo.value}</b>
          {expandIcon}
        </div>
      );
    }
    return null;
  },
};

export default function ColumnCustomRenderExample() {
  return (
    <>
      <DataSource<Developer>
        primaryKey="id"
        data={dataSource}
        defaultGroupBy={defaultGroupBy}
      >
        <InfiniteTable<Developer>
          debugId="column-render-example"
          groupColumn={groupColumn}
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

In the `column.render` function you can use hooks or [render custom React components](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.ColumnCell). To make it easier to access the param of the `render` function, we've exposed the [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) - use it to gain access to the same object that is passed as an argument to the `render` function.

**Example: Column with custom render that uses useInfiniteColumnCell**

```ts
import {
  InfiniteTable,
  DataSource,
  useInfiniteColumnCell,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';
import { HTMLProps } from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

function CustomCell(_props: HTMLProps<HTMLElement>) {
  const { value, data } = useInfiniteColumnCell<Developer>();

  let emoji = '🤷';
  switch (value) {
    case 'photography':
      emoji = '📸';
      break;
    case 'cooking':
      emoji = '👨🏻‍🍳';
      break;
    case 'dancing':
      emoji = '💃';
      break;
    case 'reading':
      emoji = '📚';
      break;
    case 'sports':
      emoji = '⛹️';
      break;
  }

  const label = data?.stack === 'frontend' ? '⚛️' : '';

  return (
    <b>
      {emoji} + {label}
    </b>
  );
}

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', maxWidth: 80 },
  firstName: { field: 'firstName' },
  hobby: {
    field: 'hobby',
    // we're not using the arg of the render function directly
    // but CustomCell uses `useInfiniteColumnCell` to retrieve it instead
    render: () => <CustomCell />,
  },
};

export default function ColumnRenderWithHooksExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="column-render-hooks-example"
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

### columns.renderFilterIcon

> Customizes the rendering of the filter icon for the column.

**Example: Custom filter icons for salary and name columns**

The `salary` column will show a bolded label when filtered.

The `firstName` column will show a custom filter icon when filtered.

```ts
import * as React from 'react';

import {
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  DataSource,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;

  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';

  salary: number;
};

const data: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: ({ filtered }) => {
      return filtered ? <b>Salary</b> : 'Salary';
    },

    renderFilterIcon: () => {
      return null;
    },
  },

  firstName: {
    field: 'firstName',
    renderFilterIcon: ({ filtered }) => {
      return filtered ? '🔥' : '';
    },
  },
  stack: { field: 'stack' },
  currency: { field: 'currency' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          defaultFilterValue={[]}
          filterDelay={0}
          filterMode="local"
        >
          <InfiniteTable<Developer>
            debugId="column-filter-icon-example"
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

### columns.renderGroupIcon (`(cellContext) => Renderable`)

> Customizes the rendering of the collapse/expand group icon for group rows. The argument passed to the function is an object of type [`InfiniteTableColumnCellContextType`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnCellContextType)

For actual content of group cells, see related [`columns.renderGroupValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupValue)

To understand how the rendering pipeline works, head over to the page on [Column rendering](https://infinite-table.com/docs/learn/columns/column-rendering.md#rendering-pipeline).

**Example: Column with custom renderGroupIcon**

```tsx
import {
  InfiniteTable,
  DataSource,
  DataSourcePropGroupBy,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  stack: {
    field: 'stack',
    renderGroupValue: ({ rowInfo, value }) => {
      return (
        <>
          {value} → {rowInfo.value} stuff
        </>
      );
    },
    renderLeafValue: ({ value, rowInfo }) => {
      return (
        <b>
          🎇 {value} → {rowInfo.value}
        </b>
      );
    },
  },
  firstName: {
    field: 'firstName',
  },

  preferredLanguage: { field: 'preferredLanguage' },
};
const groupBy: DataSourcePropGroupBy<Developer> = [
  {
    field: 'country',
    column: {
      header: 'Country group',
      renderGroupValue: ({ value }) => <>Country: {value}</>,
    },
  },
];

export default function App() {
  return (
    <DataSource<Developer> data={dataSource} primaryKey="id" groupBy={groupBy}>
      <InfiniteTable<Developer>
        debugId="column-renderGroupValueAndRenderLeafValue-example"
        columns={columns}
      />
    </DataSource>
  );
}
```

### columns.renderMenuIcon (`boolean|(cellContext)=> ReactNode`)

> Allows customization of the context menu icon.

Use this prop to customize the context icon for the current column. Specify `false` for no context menu icon.

Use a function to render a custom icon. The function is called with an object that has the following properties:

- `column`
- `columnApi` - an API object for controlling the column programatically (toggle sort, toggle column context menu, etc)

**Example: Custom menu icons and custom menu items**

In this example, the currency and preferredLanguage columns have a custom icon for triggering the column context menu.

In addition, the `preferredLanguage` column has a custom header that shows a button for triggering the column context menu.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  currency: {
    field: 'currency',

    // custom menu icon
    renderMenuIcon: () => <div>🌎</div>,
  },

  preferredLanguage: {
    field: 'preferredLanguage',
    defaultWidth: 350,
    header: ({ columnApi, renderLocation }) => {
      // if we're inside the column menu with all columns, return only the col name
      if (renderLocation === 'column-menu') {
        return 'Preferred Language';
      }

      // but for the real column header
      // return this custom content
      return (
        <>
          Preferred Language{' '}
          <button
            // we need to stop propagation so we don't trigger a sort when this button is clicked
            onPointerDown={(e) => e.stopPropagation()}
            onMouseDown={(e) => {
              // again, stop propagation so the menu is not closed automatically
              // so we can control it in the line below
              e.stopPropagation();

              columnApi.toggleContextMenu(e.target);
            }}
            style={{ border: '1px solid magenta', margin: 2 }}
          >
            Toggle menu
          </button>
        </>
      );
    },

    // custom menu icon
    renderMenuIcon: () => <div>🌎</div>,
  },
  salary: {
    field: 'salary',
    // hide the menu icon
    renderMenuIcon: false,
  },
  country: {
    field: 'country',
  },
  id: { field: 'id', defaultWidth: 80, renderMenuIcon: false },
  firstName: {
    field: 'firstName',
  },
};

export default function ColumnContextMenuItems() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="getColumnMenuItems-example"
          columnHeaderHeight={70}
          columns={columns}
          getColumnMenuItems={(items, { column }) => {
            if (column.id === 'firstName') {
              // you can adjust the default items for a specific column
              items.splice(0, 0, {
                key: 'firstName',
                label: 'First name menu item',
                onAction: () => {
                  console.log('Hey there!');
                },
              });
            }

            items.push(
              {
                key: 'hello',
                label: 'Hello World',
                onAction: () => {
                  alert('Hello World from column ' + column.id);
                },
              },
              {
                key: 'translate',
                label: 'Translate',
                menu: {
                  items: [
                    {
                      key: 'translateToEnglish',
                      label: 'English',
                      onAction: () => {
                        console.log('Translate to English');
                      },
                    },
                    {
                      key: 'translateToFrench',
                      label: 'French',
                      onAction: () => {
                        console.log('Translate to French');
                      },
                    },
                  ],
                },
              },
            );
            return items;
          }}
        />
      </DataSource>
    </>
  );
}
```

### columns.renderSelectionCheckBox (`boolean | ({ data, rowSelected: boolean | null, selectRow, deselectRow, ... })`)

> Specifies that the current column will have a selection checkbox - if a function is provided, will be used to customizes the rendering of the checkbox rendered for selection.

See related [`rowSelection`](https://infinite-table.com/docs/reference/datasource-props/index.md#rowSelection).

If `true` is provided, the default selection checkbox will be rendered. When a function is provided, it will be used for rendering the checkbox for selection.

`rowSelected` property in the function parameter can be either `boolean` or `null`. The `null` value is used for groups with indeterminate state, meaning the group has some children selected, but not all of them.

To understand how the rendering pipeline works, head over to the page on [Column rendering](https://infinite-table.com/docs/learn/columns/column-rendering.md#rendering-pipeline).

**Example: Column with custom renderSelectionCheckBox**

This example shows how you can use the default selection checkbox and decorate it.

```tsx
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
  InfiniteTableProps,
  InfiniteTablePropColumns,
  DataSourceProps,
} from '@infinite-table/infinite-react';
import * as React from 'react';

const columns: InfiniteTablePropColumns<Developer> = {
  country: {
    field: 'country',
  },
  firstName: {
    field: 'firstName',
    defaultHiddenWhenGroupedBy: '*',
  },
  stack: {
    field: 'stack',
    renderGroupValue: ({ value }) => `Stack: ${value || ''}`,
  },
  age: { field: 'age' },
  id: { field: 'id' },
  preferredLanguage: {
    field: 'preferredLanguage',
    renderGroupValue: ({ value }) => `Lang: ${value || ''}`,
  },
  canDesign: {
    field: 'canDesign',
    renderGroupValue: ({ value }) => `Can design: ${value || ''}`,
  },
};

const defaultGroupBy: DataSourceProps<Developer>['groupBy'] = [
  {
    field: 'canDesign',
  },
  {
    field: 'stack',
  },
  {
    field: 'preferredLanguage',
  },
];

const groupColumn: InfiniteTableProps<Developer>['groupColumn'] = {
  field: 'firstName',
  renderSelectionCheckBox: ({ renderBag }) => {
    // render the default value and decorate it
    return <b>[{renderBag.selectionCheckBox}]</b>;
  },
  defaultWidth: 300,
};

const domProps = {
  style: {
    flex: 1,
    minHeight: 500,
  },
};

export default function App() {
  return (
    <DataSource<Developer>
      data={dataSource}
      groupBy={defaultGroupBy}
      selectionMode="multi-row"
      primaryKey="id"
    >
      <InfiniteTable<Developer>
        debugId="column-renderSelectionCheckBox-example"
        columns={columns}
        domProps={domProps}
        groupColumn={groupColumn}
        columnDefaultWidth={150}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

### columns.renderGroupValue (`({ data, rowInfo, column, renderBag, rowIndex, ... })`)

> Customizes the rendering of a group column content, but only for group rows.

This prop is different from [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render), [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue), as it is only called for group rows.

This function prop is called with a parameter - the `value` property of this parameter is not useful for group rows (of non-group columns), as it refers to the current data item, which is a group item, not a normal data item. Instead, use `rowInfo.value`, as that's the current group row value.

See related [`columns.renderGroupIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupIcon) for customizing the collapse/expand group icon.
See related [`columns.renderLeafValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderLeafValue) for customizing the value for non-group rows in a group column.

**Example: Column with custom renderGroupValue**

```tsx
import {
  InfiniteTable,
  DataSource,
  DataSourcePropGroupBy,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  stack: {
    field: 'stack',
    renderGroupValue: ({ rowInfo, value }) => {
      return (
        <>
          {value} → {rowInfo.value} stuff
        </>
      );
    },
    renderLeafValue: ({ value, rowInfo }) => {
      return (
        <b>
          🎇 {value} → {rowInfo.value}
        </b>
      );
    },
  },
  firstName: {
    field: 'firstName',
  },

  preferredLanguage: { field: 'preferredLanguage' },
};
const groupBy: DataSourcePropGroupBy<Developer> = [
  {
    field: 'country',
    column: {
      header: 'Country group',
      renderGroupValue: ({ value }) => <>Country: {value}</>,
    },
  },
];

export default function App() {
  return (
    <DataSource<Developer> data={dataSource} primaryKey="id" groupBy={groupBy}>
      <InfiniteTable<Developer>
        debugId="column-renderGroupValueAndRenderLeafValue-example"
        columns={columns}
      />
    </DataSource>
  );
}
```

### columns.renderLeafValue (`({ data, rowInfo, column, renderBag, rowIndex, ... })`)

> Customizes the rendering of the group column content, but only for non-group rows.

See related [`columns.renderGroupValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupValue) for customizing the value for group rows in a group column.

**Example: Column with custom renderLeafValue**

```tsx
import {
  InfiniteTable,
  DataSource,
  DataSourcePropGroupBy,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  stack: {
    field: 'stack',
    renderGroupValue: ({ rowInfo, value }) => {
      return (
        <>
          {value} → {rowInfo.value} stuff
        </>
      );
    },
    renderLeafValue: ({ value, rowInfo }) => {
      return (
        <b>
          🎇 {value} → {rowInfo.value}
        </b>
      );
    },
  },
  firstName: {
    field: 'firstName',
  },

  preferredLanguage: { field: 'preferredLanguage' },
};
const groupBy: DataSourcePropGroupBy<Developer> = [
  {
    field: 'country',
    column: {
      header: 'Country group',
      renderGroupValue: ({ value }) => <>Country: {value}</>,
    },
  },
];

export default function App() {
  return (
    <DataSource<Developer> data={dataSource} primaryKey="id" groupBy={groupBy}>
      <InfiniteTable<Developer>
        debugId="column-renderGroupValueAndRenderLeafValue-example"
        columns={columns}
      />
    </DataSource>
  );
}
```

### columns.renderValue (`(cellContext) => Renderable`)

> Customizes the rendering of the column content. The argument passed to the function is an object of type [`InfiniteTableColumnCellContextType`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnCellContextType)

See related [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render), [`columns.renderGroupValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupValue)

The difference between [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) and [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) is only for special columns (for now, only group columns are special columns, but more will come) when `InfiniteTable` renders additional content inside the column (eg: collapse/expand tool for group rows). The [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) function allows you to override the additional content. So if you specify this function, it's up to you to render whatever content, including the collapse/expand tool.

Note that for customizing the collapse/expand tool, you can use specify `renderGroupIcon` function on the group column.

To understand how the rendering pipeline works, head over to the page on [Column rendering](https://infinite-table.com/docs/learn/columns/column-rendering.md#rendering-pipeline).

The [renderValue](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) and [render](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) functions are called with an object that has the following properties:

- data - the data object (of type `DATA_TYPE | Partial<DATA_TYPE> | null`) for the row.
- rowInfo - very useful information about the current row. See [Using RowInfo](https://infinite-table.com/docs/learn/rows/using-row-info.md) for more details.
- renderBag - read more about this in the docs for [Column rendering pipeline](https://infinite-table.com/docs/learn/columns/column-rendering.md#rendering-pipeline)

**Example: Column with custom renderValue**

```tsx
import {
  InfiniteTable,
  DataSource,
  DataSourceGroupBy,
  InfiniteTablePropGroupColumn,
  InfiniteTableColumnRenderValueParam,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  stack: {
    field: 'stack',
    renderValue: ({ data, rowInfo }) => {
      if (rowInfo.isGroupRow) {
        return <>{rowInfo.value} stuff</>;
      }

      return <b>🎇 {data?.stack}</b>;
    },
  },
  firstName: {
    field: 'firstName',
  },

  preferredLanguage: { field: 'preferredLanguage' },
};

const defaultGroupBy: DataSourceGroupBy<Developer>[] = [{ field: 'stack' }];

const groupColumn: InfiniteTablePropGroupColumn<Developer> = {
  defaultWidth: 250,

  renderValue: ({
    rowInfo,
  }: InfiniteTableColumnRenderValueParam<Developer>) => {
    if (rowInfo.isGroupRow) {
      return (
        <>
          Grouped by <b>{rowInfo.value}</b>
        </>
      );
    }
    return null;
  },
};

export default function ColumnValueGetterExample() {
  return (
    <>
      <DataSource<Developer>
        primaryKey="id"
        data={dataSource}
        defaultGroupBy={defaultGroupBy}
      >
        <InfiniteTable<Developer>
          debugId="column-renderValue-example"
          groupColumn={groupColumn}
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

In the `column.renderValue` function you can use hooks or [render custom React components](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.ColumnCell). To make it easier to access the param of the `renderValue` function, we've exposed the [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) - use it to gain access to the same object that is passed as an argument to the `renderValue` function.

**Example: Using a sparkline component**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import { Sparklines, SparklinesLine } from 'react-sparklines';

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  bugFixes: number[];
  streetName: string;
  streetPrefix: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  currency: number;
  age: number;
  email: string;
};
export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
    columnGroup: 'location',
  },
  bugFixes: {
    field: 'bugFixes',
    header: 'Bug Fixes',
    defaultWidth: 300,
    renderValue: ({ value, data }) => {
      const color =
        data?.department === 'IT' || data?.department === 'Management'
          ? 'tomato'
          : '#253e56';
      return (
        <Sparklines
          data={value}
          style={{
            width: '100%',
          }}
          height={30}
        >
          <SparklinesLine color={color} />
        </Sparklines>
      );
    },
  },
  city: {
    field: 'city',
    header: 'City',
    columnGroup: 'address',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
  department: {
    field: 'department',
    header: 'Department',
  },
  team: {
    field: 'team',
    header: 'Team',
  },
};

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees10k')
    .then((r) => r.json())
    .then((data: Employee[]) => {
      return data.map((employee) => {
        return {
          ...employee,
          bugFixes: [...Array(10)].map(() => Math.round(Math.random() * 100)),
        };
      });
    });
};

export default function App() {
  return (
    <DataSource<Employee> data={dataSource} primaryKey="id">
      <InfiniteTable<Employee>
        debugId="using-sparklines-example"
        columns={columns}
        columnDefaultWidth={150}
      />
    </DataSource>
  );
}
```

### columns.resizable (`boolean`)

> Specifies if the current column is resizable or not.

By default, all columns are resizable, since [`resizableColumns`](https://infinite-table.com/docs/reference/infinite-table-props.md#resizableColumns) defaults to `true`.

### columns.rowspan (`({ rowInfo, data, rowIndex, column }) => number`)

> Specifies the rowspan for cells on the current column.

The default rowspan for a column cell is 1. If you want to span multiple rows, return a value that is greater than 1.

This function is called with an object that has the following properties:

- column - the current column
- data - the current data
- rowInfo - information about the current row

The `rowInfo` object contains information about grouping (if this row is a group row, the collapsed state, etc), parent groups, children of the current row (if it's a row group), etc. See [Using RowInfo](https://infinite-table.com/docs/learn/rows/using-row-info.md) for more details.

**Example**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
  DataSourceGroupBy,
  InfiniteTableGroupColumnBase,
} from '@infinite-table/infinite-react';
import * as React from 'react';

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 columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id' },
  firstName: { field: 'firstName' },
  preferredLanguage: {
    field: 'preferredLanguage',
  },
  stack: { field: 'stack' },
  country: { field: 'country' },
  canDesign: { field: 'canDesign' },
  hobby: { field: 'hobby' },

  city: {
    field: 'city',
  },
  age: { field: 'age' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  currency: { field: 'currency' },
};

const defaultGroupBy: DataSourceGroupBy<Developer>[] = [
  { field: 'stack' },
  { field: 'preferredLanguage' },
  {
    field: 'country',
    column: {
      rowspan: ({ rowInfo }) => {
        const rowspan =
          rowInfo.isGroupRow && rowInfo.groupNesting === 3 && !rowInfo.collapsed
            ? (rowInfo.deepRowInfoArray?.length || 0) + 1
            : 1;

        return rowspan;
      },
    } as InfiniteTableGroupColumnBase<Developer>,
  },
];

export default function App() {
  return (
    <DataSource<Developer>
      data={dataSource}
      defaultGroupBy={defaultGroupBy}
      primaryKey="id"
    >
      <InfiniteTable<Developer>
        debugId="column-rowspan-example"
        columns={columns}
        columnDefaultWidth={250}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### columns.shouldAcceptEdit (`(params) => boolean|Error|Promise<boolean|Error>`)

> Function specified for the column, that determines whether to accept an edit or not.

This function is called when the user wants to finish an edit. The function is used to decide whether an edit is accepted or rejected.

<p>When the global [`shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#shouldAcceptEdit) prop is specified, this is no longer called, and instead the global one is called.</p>
<p>If you define the global [`shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#shouldAcceptEdit) and still want to use the column-level function, you can call the column-level function from the global one.</p>

The function is called with an object that has the following properties:

- `value` - the value that the user wants to persist via the cell editor
- `initialValue` - the initial value of the cell (the value that was displayed before editing started). This is the value resulting after [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) and [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) have been applied)
- `rawValue` - the initial value of the cell, but before any formatting and custom rendering has been applied. This is either the field value from the current data object, or the result of the column [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) function.
- `data` - the current data object
- `rowInfo` - the row info object that underlies the row
- `column` - the current column on which editing is invoked
- `api` - a reference to the [InfiniteTable API](https://infinite-table.com/docs/reference/api/index.md)
- `dataSourceApi` - - a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)

**Example**

Try editing the `salary` column. In the editor you can write whatever, but the column will only accept edits that are valid numbers.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  currency: string;
  stack: string;
  hobby: string;
  salary: string;
};
const dataSource: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    currency: 'USD',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'USD 1000',
  },
  {
    id: 2,
    firstName: 'Jane',
    currency: 'EUR',
    stack: 'backend',
    hobby: 'reading',
    salary: 'EUR 2000',
  },
  {
    id: 3,
    firstName: 'Jack',
    currency: 'GBP',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'GBP 3000',
  },
  {
    id: 4,
    firstName: 'Jill',
    currency: 'USD',
    stack: 'backend',
    hobby: 'reading',
    salary: 'USD 4000',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80, defaultEditable: false },

  salary: {
    defaultWidth: 320,
    field: 'salary',
    header: 'Salary - edit accepts numbers only',
    style: { color: 'tomato' },
    getValueToEdit: ({ value }) => {
      return parseInt(value.substr(4), 10);
    },
    getValueToPersist: ({ value, data }) => {
      return `${data!.currency} ${parseInt(value, 10)}`;
    },
    shouldAcceptEdit: ({ value }) => {
      return parseInt(value, 10) == value;
    },
  },

  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  currency: {
    field: 'currency',
    header: 'Currency',
  },
};

export default function InlineEditingExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="inline-editing-custom-edit-value-example"
          columns={columns}
          columnDefaultEditable
        />
      </DataSource>
    </>
  );
}
```

### columns.sortable (`boolean`)

> Specifies the sorting behavior for the current column. Overrides the global [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable) prop.

Use this column property in order to explicitly make the column sortable or not sortable. If not specified, the sortable prop from the column type ([`columnTypes.sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.sortable)) will be used. If that is not specified either, the global [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable) prop will be used.

### columns.sortType (`string | string[]`)

> Specifies the sort type for the column. See related [`sortTypes`](https://infinite-table.com/docs/reference/datasource-props/index.md#sortTypes)

For local sorting, the sort order for a column is determined by the specified `sortType`.

- if no `sortType` is specified, the [column.dataType](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.dataType) will be used as the `sortType`
- if no `sortType` or `dataType` is specified, it will default to the [`columns.type`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) value (if an array, the first item will be used).
- if none of those are specified `"string"` is used

The value of this prop (as specified, or as computed by the steps described above) should be a key from the [`sortTypes`](https://infinite-table.com/docs/reference/datasource-props/index.md#sortTypes) object.

**Example: Custom sort by color - magenta will come first**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';

export type CarSale = {
  id: number;

  make: string;
  model: string;
  year: number;

  sales: number;
  color: string;
};

const carsales: CarSale[] = [
  {
    make: 'Volkswagen',
    model: 'GTI',
    year: 2009,
    sales: 6,
    color: 'red',
    id: 0,
  },
  {
    make: 'Honda',
    model: 'Element 2WD',
    year: 2009,
    sales: 739,
    color: 'red',
    id: 1,
  },
  {
    make: 'Acura',
    model: 'RDX 4WD',
    year: 2008,
    sales: 2,
    color: 'magenta',
    id: 2,
  },
  {
    make: 'Honda',
    model: 'Fit',
    year: 2009,
    sales: 211,
    color: 'blue',
    id: 3,
  },
  {
    make: 'Mazda',
    model: '6',
    year: 2009,
    sales: 31,
    color: 'blue',

    id: 4,
  },
  {
    make: 'Acura',
    model: 'TSX',
    year: 2009,
    sales: 14,
    color: 'yellow',
    id: 5,
  },
  {
    make: 'Acura',
    model: 'TSX',
    year: 2010,
    sales: 14,
    color: 'red',
    id: 6,
  },
  {
    make: 'Audi',
    model: 'A3',
    year: 2009,
    sales: 2,
    color: 'magenta',
    id: 7,
  },
];

const columns: Record<string, InfiniteTableColumn<CarSale>> = {
  color: { field: 'color', sortType: 'color' },
  make: { field: 'make' },
  model: { field: 'model' },

  sales: {
    field: 'sales',
    sortType: 'number',
  },
  year: {
    field: 'year',
    sortType: 'number',
  },
};
const newSortTypes = {
  color: (one: string, two: string) => {
    if (one === 'magenta') {
      // magenta comes first
      return -1;
    }
    if (two === 'magenta') {
      // magenta comes first
      return 1;
    }
    return one.localeCompare(two);
  },
};

export default function DataTestPage() {
  return (
    <>
      <DataSource<CarSale>
        data={carsales}
        primaryKey="id"
        defaultSortInfo={{
          field: 'color',
          dir: 1,
          type: 'color',
        }}
        sortTypes={newSortTypes}
      >
        <InfiniteTable<CarSale> debugId="sortTypes-example" columns={columns} />
      </DataSource>
    </>
  );
}
```

For group columns (and more specifically, when [`groupRenderStrategy`](https://infinite-table.com/docs/reference/infinite-table-props.md#groupRenderStrategy) is `single-column`), the `sortType` should be a `string[]`, each item in the array corresponding to an item in [`groupBy`](https://infinite-table.com/docs/reference/datasource-props/index.md#groupBy) of the `<DataSource />`. This is especially useful when there are no corresponding columns for the `groupBy` fields. In this case, `InfiniteTable` can't know the type of sorting those fields will require, so you have to provide it yourself via the `column.sortType`.

### columns.style (`CSSProperties | (param: InfiniteTableColumnStyleFnParams) => CSSProperties`)

> Controls styling for the column. Can be a style object or a function returning a style object.

If defined as a function, it accepts an object as a parameter (of type [`InfiniteTableColumnStylingFnParams`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnStylingFnParams)), which has the following properties:

- `column` - the current column where the style is being applied
- `data` - the data object for the current row. The type of this object is `DATA_TYPE | Partial<DATA_TYPE> | null`. For regular rows, it will be of type `DATA_TYPE`, while for group rows it will be `Partial<DATA_TYPE>`. For rows not yet loaded (because of batching being used), it will be `null`.
- `rowInfo` - the information about the current row - see [Using RowInfo](https://infinite-table.com/docs/learn/rows/using-row-info.md) for more details.
- `value` - the underlying value of the current cell - will generally be `data[column.field]`, if the column is bound to a `field` property
- ... and more, see [`InfiniteTableColumnStylingFnParams`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableColumnStylingFnParams) for details

The `style` property can also be specified for [`columnTypes`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes)

**Example**

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    style: {
      background: 'gray',
      color: 'white',
    },
  },
  firstName: { field: 'firstName' },
  preferredLanguage: { field: 'preferredLanguage' },
  salary: {
    field: 'salary',
    type: 'number',
    style: ({ value }) => {
      return {
        color: value && value > 100_000 ? 'red' : 'currentColor',
      };
    },
  },
  stack: { field: 'stack' },
  country: { field: 'country' },
  age: { field: 'age', type: 'number' },

  currency: { field: 'currency', type: 'number' },
};

export default function GroupByExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="columns-style-example"
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

### columns.type (`string | string[]`)

> Specifies the column type - a column type is a set of properties that describes the column. Column types allow to easily apply the same properties to multiple columns.

Specifying `type: "number"` for numeric columns will ensure correct number sorting function is used (when sorting is done client-side). This happens because [`sortTypes`](https://infinite-table.com/docs/reference/datasource-props/index.md#sortTypes) has a definition for the `number` sort type.

For date columns (where the values in the columns are actual date objects) specify `type: "date"`. [Read more about date columns here](https://infinite-table.com/docs/learn/working-with-data/handling-dates.md#using-date-strings)

See [`columnTypes`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes) for more details on using column types.

By default, all columns have the `default` column type applied. So, if you define the `default` column type, but don't specify any [type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) for a column, the default column type properties will be applied to that column.

When you want both the default type and another type to be applied, you can do so by specifying `type: ["default", "second-type"]`.

When you dont want the default type to be applied, use `type: null`.

If a column is filterable and does not explicitly specify a [filterType](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.filterType), the `type` will also be used as the filter type.

If a column is sortable and does not explicitly specify a [sortType](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.sortType), the `type` will also be used as the sort type.

See the example below - `id` and `age` columns are `type='number'`.

**Example**

```ts files=["columns-example.page.tsx","data.ts"]

```

### columns.valueFormatter (`({ data?, isGroupRow, rowInfo, field?, rowSelected, rowActive, isGroupRow }) => Renderable`)

> Customizes the value that will be rendered

The `valueFormatter` prop is the next function called after the [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) during the [rendering pipeline](https://infinite-table.com/docs/learn/columns/column-rendering.md#rendering-pipeline). Unlike [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter), [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) can return any renderable value, like `JSX.Element`s.

Unlike `valueGetter`, it is being called with an object that has both the `data` item (might be null or partial for group rows) and the `rowInfo` object, and some extra flags regarding the row state (selection, active, etc). Use the TS `isGroupRow` flag as discriminator to decide if `data` is available.

If you want to further customize what's being rendered, see related [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter), [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue), [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render), [`columns.renderGroupValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupValue), [`columns.renderLeafValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderLeafValue) and [`columns.renderGroupIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupIcon).

**Example: Column with custom valueFormatter**

```tsx
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  name: {
    header: 'Full Name',
    valueFormatter: ({ data, isGroupRow, value }) => {
      if (isGroupRow) {
        return <b>{value}</b>;
      }
      return (
        <b>
          <pre>
            {data.firstName}, {data.lastName}
          </pre>
        </b>
      );
    },
  },

  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },
};

export default function ColumnValueFormatterExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="column-valueFormatter-example"
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

### columns.valueGetter (`({ data, field? }) => string | number | boolean | null | undefined`)

> Customizes the value that will be rendered

The `valueGetter` prop is a function that takes a single argument - an object with `data` and `field` properties. It should return a plain JavaScript value (so not a `ReactNode` or `JSX.Element`)

Note that the `data` property is of type `DATA_TYPE | Partial<DATA_TYPE> | null` and not simply `DATA_TYPE`, because there are cases when you can have grouping (so for group rows with aggregations `data` will be `Partial<DATA_TYPE>`) or when there are lazily loaded rows or group rows with no aggregations - for which `data` is still `null`.

If you want to further customize what's being rendered, see related [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter), [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue), [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render), [`columns.renderGroupValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupValue), [`columns.renderLeafValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderLeafValue) and [`columns.renderGroupIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderGroupIcon).

**Example: Column with custom valueGetter**

```tsx
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  name: {
    header: 'Full Name',
    valueGetter: ({ data }) => `${data.firstName} ${data.lastName}`,
  },

  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },
};

export default function ColumnValueGetterExample() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="column-valueGetter-example"
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

### columnSizing (`Record<string,{width,flex,...}>`)

> Defines the sizing of columns in the grid.

This is a controlled property. For the uncontrolled version, see [`defaultColumnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing).

It is an object that maps column ids to column sizing options. The values in the objects can contain the following properties:

- [flex](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.flex) - use this for flexible columns. Behaves like the `flex` CSS property.
- [width](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.width) - use this for fixed sized columns
- [minWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.minWidth) - specifies the minimum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.
- [maxWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.maxWidth) - specifies the maximum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.

**Example: Controlled column sizing**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },

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

  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [columnSizing, setColumnSizing] =
    React.useState<InfiniteTablePropColumnSizing>({
      country: { width: 100 },
      city: { flex: 1, minWidth: 100 },
      salary: { flex: 2, maxWidth: 500 },
    });

  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);

  return (
    <>
      <p style={{ color: 'var(--infinite-cell-color)' }}>
        Current column sizing:{' '}
        <code>
          <pre>{JSON.stringify(columnSizing, null, 2)}</pre>
        </code>
        Viewport reserved width: {viewportReservedWidth} -{' '}
        <button onClick={() => setViewportReservedWidth(0)}>
          click to reset to 0
        </button>
      </p>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="columnSizing-example"
          columns={columns}
          columnDefaultWidth={50}
          columnSizing={columnSizing}
          onColumnSizingChange={setColumnSizing}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

For auto-sizing columns, see [`autoSizeColumnsKey`](https://infinite-table.com/docs/reference/infinite-table-props.md#autoSizeColumnsKey).

### columnSizing.flex (`number`)

> Specifies the flex value for the column.

See [using flexible column sizing section](https://infinite-table.com/docs/learn/columns/fixed-and-flexible-size.md#using-flexible-column-sizing) for more details.

A column can either be flexible or fixed-width. For fixed columns, use [`columnSizing.width`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.width) if you're using [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) or [column.defaultWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth) for default-uncontrolled sizing.

**Example: Controlled column sizing with flex columns**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },

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

  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [columnSizing, setColumnSizing] =
    React.useState<InfiniteTablePropColumnSizing>({
      country: { width: 100 },
      city: { flex: 1, minWidth: 100 },
      salary: { flex: 2, maxWidth: 500 },
    });

  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);

  return (
    <>
      <p style={{ color: 'var(--infinite-cell-color)' }}>
        Current column sizing:{' '}
        <code>
          <pre>{JSON.stringify(columnSizing, null, 2)}</pre>
        </code>
        Viewport reserved width: {viewportReservedWidth} -{' '}
        <button onClick={() => setViewportReservedWidth(0)}>
          click to reset to 0
        </button>
      </p>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="columnSizing-example"
          columns={columns}
          columnDefaultWidth={50}
          columnSizing={columnSizing}
          onColumnSizingChange={setColumnSizing}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### columnSizing.minWidth (`number`)

> Specifies the minimum width for a column. Especially useful for flexible columns.

See [Using flexible column sizing](https://infinite-table.com/docs/learn/columns/fixed-and-flexible-size.md#using-flexible-column-sizing) for more details on the flex algorithm.

This can also be specified for all columns by specifying [`columnMinWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMinWidth).

**Example: Controlled column sizing with minWidth for column**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },

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

  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [columnSizing, setColumnSizing] =
    React.useState<InfiniteTablePropColumnSizing>({
      country: { width: 100 },
      city: { flex: 1, minWidth: 100 },
      salary: { flex: 2, maxWidth: 500 },
    });

  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);

  return (
    <>
      <p style={{ color: 'var(--infinite-cell-color)' }}>
        Current column sizing:{' '}
        <code>
          <pre>{JSON.stringify(columnSizing, null, 2)}</pre>
        </code>
        Viewport reserved width: {viewportReservedWidth} -{' '}
        <button onClick={() => setViewportReservedWidth(0)}>
          click to reset to 0
        </button>
      </p>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="columnSizing-example"
          columns={columns}
          columnDefaultWidth={50}
          columnSizing={columnSizing}
          onColumnSizingChange={setColumnSizing}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### columnSizing.maxWidth (`number`)

> Specifies the maximum width for a column. Especially useful for flexible columns.

See [Using flexible column sizing](https://infinite-table.com/docs/learn/columns/fixed-and-flexible-size.md#using-flexible-column-sizing) for more details on the flex algorithm.

This can also be specified for all columns by specifying [`columnMaxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnMaxWidth).

**Example: Controlled column sizing with maxWidth for column**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },

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

  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [columnSizing, setColumnSizing] =
    React.useState<InfiniteTablePropColumnSizing>({
      country: { width: 100 },
      city: { flex: 1, minWidth: 100 },
      salary: { flex: 2, maxWidth: 500 },
    });

  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);

  return (
    <>
      <p style={{ color: 'var(--infinite-cell-color)' }}>
        Current column sizing:{' '}
        <code>
          <pre>{JSON.stringify(columnSizing, null, 2)}</pre>
        </code>
        Viewport reserved width: {viewportReservedWidth} -{' '}
        <button onClick={() => setViewportReservedWidth(0)}>
          click to reset to 0
        </button>
      </p>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="columnSizing-example"
          columns={columns}
          columnDefaultWidth={50}
          columnSizing={columnSizing}
          onColumnSizingChange={setColumnSizing}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### columnSizing.width (`number`)

> Specifies the fixed width for the column.

See [Using flexible column sizing](https://infinite-table.com/docs/learn/columns/fixed-and-flexible-size.md#using-flexible-column-sizing) for more details.

A column can either be flexible or fixed. For flexible columns, use [`columnSizing.flex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.flex).

**Example: Controlled column sizing with fixed column**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },

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

  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [columnSizing, setColumnSizing] =
    React.useState<InfiniteTablePropColumnSizing>({
      country: { width: 100 },
      city: { flex: 1, minWidth: 100 },
      salary: { flex: 2, maxWidth: 500 },
    });

  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);

  return (
    <>
      <p style={{ color: 'var(--infinite-cell-color)' }}>
        Current column sizing:{' '}
        <code>
          <pre>{JSON.stringify(columnSizing, null, 2)}</pre>
        </code>
        Viewport reserved width: {viewportReservedWidth} -{' '}
        <button onClick={() => setViewportReservedWidth(0)}>
          click to reset to 0
        </button>
      </p>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="columnSizing-example"
          columns={columns}
          columnDefaultWidth={50}
          columnSizing={columnSizing}
          onColumnSizingChange={setColumnSizing}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### keyboardShortcuts (`{key,handler,when}[]`)

> An array that specifies the keyboard shortcuts for the DataGrid.

See the [Keyboard Shortcuts](https://infinite-table.com/docs/learn/keyboard-navigation/keyboard-shortcuts.md) page for more details.

**Example**

Click on a cell and use the keyboard to navigate.

Press `Shift+Enter` to show an alert with the current active cell position.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage', header: 'Language' },
  country: { field: 'country', header: 'Country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardShortcuts() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="keyboard-shortcuts-initial-example"
          columns={columns}
          keyboardShortcuts={[
            {
              key: 'Shift+Enter',
              when: (context) => !!context.getState().activeCellIndex,
              handler: (context) => {
                const { activeCellIndex } = context.getState();

                const [rowIndex, columnIndex] = activeCellIndex!;
                alert(
                  `Current active cell: row ${rowIndex}, column ${columnIndex}.`,
                );
              },
            },
            {
              key: 'PageUp',
              handler: () => {
                console.log('PageUp key pressed.');
              },
            },
            {
              key: 'PageDown',
              handler: () => {
                console.log('PageDown key pressed.');
              },
            },
          ]}
        />
      </DataSource>
    </>
  );
}
```

Infinite Table DataGrid comes with some predefined keyboard shorcuts.
you can import from the `keyboardShortcuts` named export.
```ts
import { keyboardShortcuts } from '@infinite-table/infinite-react'
```

#### Instant Edit

```ts {4,12}
import {
  DataSource,
  InfiniteTable,
  keyboardShortcuts
} from '@infinite-table/infinite-react';

 function App() {
  return <DataSource<Developer> primaryKey="id" data={dataSource}>
    <InfiniteTable<Developer>
      columns={columns}
      keyboardShortcuts={[
        keyboardShortcuts.instantEdit
      ]}
    />
  </DataSource>
}
```

For now, the only predefined keyboard shorcut is `keyboardShortcuts.instantEdit`. This keyboard shorcut starts cell editing when any key is pressed on the active cell. This is the same behavior found in Excel/Google Sheets.

**Example**

Click on a cell and then start typing to edit the cell.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
  keyboardShortcuts,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage', header: 'Language' },
  country: { field: 'country', header: 'Country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id', defaultEditable: false },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardShortcuts() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="keyboard-shortcuts-instant-edit-example"
          columns={columns}
          columnDefaultEditable
          keyboardShortcuts={[keyboardShortcuts.instantEdit]}
        />
      </DataSource>
    </>
  );
}
```

### columnTypes (`Record<string,InfiniteTableColumnType>`)

> Specifies an object that maps column type ids to column types. Column types are used to apply the same configuration/properties to multiple columns. See related [`columns.type`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type)

By default, all columns have the `default` column type applied. So, if you define the `default` column type, but don't specify any [type](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.type) for a column, the default column type properties will be applied to that column.

The following properties are currently supported for defining a column type:

- `align` - See [`columns.align`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.align)
- `className` - See [`columns.className`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.className)
- `components` - See [`columns.components`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components)
- `cssEllipsis` - See [`columns.cssEllipsis`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.cssEllipsis)
- `defaultEditable` - See [`columns.defaultEditable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultEditable)
- `defaultFlex` - default flex value (uncontrolled) for the column(s) this column type will be applied to. See [`column.defaultFlex`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.defaultFlex)
- `defaultWidth` - default width (uncontrolled) for the column(s) this column type will be applied to. See [`column.defaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.defaultWidth)
- `getValueToEdit` - See [`columns.getValueToEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.getValueToEdit)
- `getValueToPersist` - See [`columns.getValueToPersist`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.getValueToPersist)
- `headerAlign` - See [`columns.headerAlign`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerAlign)
- `headerCssEllipsis` - See [`columns.headerCssEllipsis`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerCssEllipsis)
- `headerStyle` - See [`columns.headerStyle`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerStyle)
- `header` - See [`columns.header`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header)
- `maxWidth` - minimum width for the column(s) this column type will be applied to. See [`column.maxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.maxWidth)
- `minWidth` - minimum width for the column(s) this column type will be applied to. See [`column.minWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.minWidth)
- `renderMenuIcon` - See [`columns.renderMenuIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderMenuIcon)
- `renderSortIcon` - See [`columns.renderSortIcon`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderSortIcon)
- `renderValue` - See [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue)
- `render` - render function for the column(s) this column type will be applied to. See [`column.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.render)
- `shouldAcceptEdit` - See [`columns.shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit)
- `sortable` - See [`columns.sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.sortable)
- `style` - See [`columns.style`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.style)
- `valueFormatter` - See [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter)
- `valueGetter` - See [`columns.valueGetter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter)
- `verticalAlign` - See [`columns.verticalAlign`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.verticalAlign)

When any of the properties defined in a column type are also defined in a column (or in column sizing/pinning,etc), the later take precedence so the properties in column type are not applied.

The only exception to this rule is the [components](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components) property, which is merged from column types into the column.

**Example: Using MUI X Date Picker with custom 'date' type columns**

This is a basic example integrating with the [MUI X Date Picker](https://mui.com/x/react-date-pickers/date-picker/) - click any cell in the **Birth Date** or **Date Hired** columns to show the date picker.

This example uses the [column types](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes) to give each date column the same editor and styling.

```ts
import {
  InfiniteTable,
  DataSource,
  useInfiniteColumnEditor,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import { StyledEngineProvider } from '@mui/material/styles';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import dayjs from 'dayjs';

import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';

import * as _emotionStyled from '@emotion/styled';
import * as _emotionReact from '@emotion/react';

import * as React from 'react';

type Developer = {
  birthDate: Date;
  dateHired: Date;
  id: number;
  firstName: string;
  country: string;
  city: string;
  currency: string;
  email: string;
  preferredLanguage: string;
  hobby: string;
  salary: number;
};

const DATE_FORMAT = 'YYYY-MM-DD';

const DateEditor = () => {
  const { value, confirmEdit, cancelEdit } = useInfiniteColumnEditor();

  const day = dayjs(value);

  return (
    <DatePicker
      slotProps={{
        textField: {
          sx: {
            width: '100%',
            height: '100%',
            background: 'white',
            padding: '3px',
          },
        },
      }}
      value={day}
      open
      orientation="landscape"
      format={DATE_FORMAT}
      onError={cancelEdit}
      onClose={cancelEdit}
      onAccept={(day) => {
        if (day) {
          confirmEdit(day.toDate());
        }
      }}
    />
  );
};

const columns: InfiniteTablePropColumns<Developer> = {
  firstName: { field: 'firstName', header: 'First Name' },
  birthDate: {
    field: 'birthDate',
    header: 'Birth Date',
    // we need to specify the type of the column as "date"
    type: 'date',
  },
  dateHired: {
    field: 'dateHired',
    header: 'Date Hired',
    // we need to specify the type of the column as "date"
    type: 'date',
  },
  salary: {
    field: 'salary',
    header: 'Salary',
    type: 'number',
  },
  country: { field: 'country', header: 'Country' },
  preferredLanguage: { field: 'preferredLanguage' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

const columnTypes = {
  date: {
    defaultEditable: true,
    components: {
      Editor: DateEditor,
    },
    defaultWidth: 200,
    style: ({ inEdit }: { inEdit: boolean }) => {
      return inEdit ? { padding: 0 } : {};
    },
    renderValue: ({ value }: { value: Date }) => {
      return <b>{dayjs(value).format(DATE_FORMAT)}</b>;
    },
  },
};

export default function LocalUncontrolledSingleSortingExample() {
  return (
    <>
      <StyledEngineProvider injectFirst>
        <LocalizationProvider dateAdapter={AdapterDayjs}>
          <DataSource<Developer> primaryKey="id" data={dataSource}>
            <InfiniteTable<Developer>
              debugId="column-types-date-editor-example"
              columnTypes={columnTypes}
              columns={columns}
              columnDefaultWidth={120}
            />
          </DataSource>
        </LocalizationProvider>
      </StyledEngineProvider>
    </>
  );
}

const dataSource: Developer[] = [
  {
    id: 0,
    firstName: 'Nya',
    country: 'India',
    city: 'Unnao',
    birthDate: new Date(1997, 0, 1),
    dateHired: new Date(2023, 0, 1),
    currency: 'JPY',
    preferredLanguage: 'TypeScript',
    salary: 60000,
    hobby: 'sports',
    email: 'Nya44@gmail.com',
  },
  {
    id: 1,
    firstName: 'Axel',
    country: 'Mexico',
    city: 'Cuitlahuac',
    birthDate: new Date(1993, 3, 10),
    dateHired: new Date(2022, 5, 10),
    currency: 'USD',
    preferredLanguage: 'TypeScript',
    salary: 100000,
    hobby: 'sports',
    email: 'Axel93@hotmail.com',
  },
  {
    id: 2,
    firstName: 'Gonzalo',
    country: 'United Arab Emirates',
    city: 'Fujairah',
    birthDate: new Date(1997, 10, 30),
    dateHired: new Date(2021, 8, 29),
    currency: 'JPY',
    preferredLanguage: 'Go',
    salary: 120000,
    hobby: 'photography',
    email: 'Gonzalo_McGlynn34@gmail.com',
  },
  {
    id: 3,
    firstName: 'Sherwood',
    country: 'Mexico',
    city: 'Tlacolula de Matamoros',
    birthDate: new Date(1990, 5, 20),
    dateHired: new Date(2021, 8, 20),
    currency: 'CHF',
    preferredLanguage: 'Rust',
    salary: 99000,
    hobby: 'cooking',
    email: 'Sherwood_McLaughlin65@hotmail.com',
  },
  {
    id: 4,
    firstName: 'Alexandre',
    country: 'France',
    city: 'Persan',
    birthDate: new Date(1990, 3, 20),
    dateHired: new Date(2023, 11, 12),
    currency: 'EUR',
    preferredLanguage: 'Go',
    salary: 97000,
    hobby: 'reading',
    email: 'Alexandre_Harber@hotmail.com',
  },
  {
    id: 5,
    firstName: 'Mariane',
    country: 'United States',
    city: 'Hays',
    birthDate: new Date(2002, 3, 20),
    dateHired: new Date(2022, 2, 22),
    currency: 'EUR',
    preferredLanguage: 'TypeScript',

    salary: 58000,
    hobby: 'cooking',
    email: 'Mariane0@hotmail.com',
  },
  {
    id: 6,
    firstName: 'Rosalind',
    country: 'Mexico',
    city: 'Nuevo Casas Grandes',
    birthDate: new Date(1992, 11, 12),
    dateHired: new Date(2022, 1, 12),
    currency: 'AUD',
    preferredLanguage: 'JavaScript',
    salary: 198000,
    hobby: 'dancing',
    email: 'Rosalind69@gmail.com',
  },
  {
    id: 7,
    firstName: 'Lolita',
    country: 'Sweden',
    city: 'Delsbo',
    birthDate: new Date(1990, 9, 5),
    dateHired: new Date(2022, 1, 5),
    currency: 'JPY',
    preferredLanguage: 'TypeScript',
    salary: 200000,
    hobby: 'cooking',
    email: 'Lolita.Hayes@hotmail.com',
  },
  {
    id: 8,
    firstName: 'Tre',
    country: 'Germany',
    city: 'Bad Camberg',
    birthDate: new Date(1990, 9, 15),
    dateHired: new Date(2022, 10, 1),
    currency: 'GBP',
    preferredLanguage: 'TypeScript',
    salary: 200000,
    hobby: 'sports',
    email: 'Tre28@gmail.com',
  },
  {
    id: 9,
    firstName: 'Lurline',
    country: 'Canada',
    city: 'Raymore',
    birthDate: new Date(1990, 4, 18),
    dateHired: new Date(2023, 3, 18),
    currency: 'EUR',
    preferredLanguage: 'Rust',
    salary: 58000,
    hobby: 'sports',
    email: 'Lurline_Deckow@gmail.com',
  },
];
```

### columnTypes.components

> See related [`columns.components`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components).

### columnTypes.defaultFlex (`number`)

> Specifies a default flex value for the column type. Will be overriden in any column that already specifies a `defaultFlex` property.

See related [`columnTypes.defaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.defaultWidth), [`columns.defaultFlex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex) and [`columns.defaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth)

### columnTypes.defaultSortable (`boolean`)

> Specifies whether columns of this type are sortable.

This prop overrides the component-level [`columnDefaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultSortable).

This prop is overriden by [`columns.defaultSortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultSortable) and [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable).

### columnTypes.headerClassName (`string | (args) => string`)

> Controls styling for the column header for columns with this column type. Can be a string or a function returning a string.

See docs at [`columns.headerClassName`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerClassName).

### columns.align (`'start' | 'center' | 'end'`)

> Controls the alignment of text in column cells and also the alignment of the column header. To only apply alignment to the column header, use [`columns.headerAlign`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerAlign). For vertical alignment, see [`columns.verticalAlign`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.verticalAlign).

For css ellipsis, see [`columns.cssEllipsis`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.cssEllipsis).

**Example: Column align example**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

export default function App() {
  const [align, setAlign] = React.useState<'start' | 'center' | 'end'>('start');

  const columns: InfiniteTablePropColumns<Developer> = {
    firstName: {
      field: 'firstName',

      align,
    },
  };
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: '10px',
        }}
      >
        <p>Select the column align</p>
        <select
          value={align}
          onChange={(e) =>
            setAlign(e.target.value as 'start' | 'center' | 'end')
          }
        >
          <option value="start">Start</option>
          <option value="center">Center</option>
          <option value="end">End</option>
        </select>
      </div>
      <DataSource<Developer> data={dataSource} primaryKey="id">
        <InfiniteTable<Developer>
          debugId="column-align-example"
          columns={columns}
          columnDefaultWidth={250}
          headerOptions={{
            alwaysReserveSpaceForSortIcon: false,
          }}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

### columns.verticalAlign (`'start' | 'center' | 'end'`)

> Controls the vertical alignment of text in column cells. For horizontal alignment, see [`columns.align`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.align).

**Example: Column vertical align example**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

export default function App() {
  const [verticalAlign, setVerticalAlign] = React.useState<
    'start' | 'center' | 'end'
  >('center');

  const columns: InfiniteTablePropColumns<Developer> = {
    firstName: {
      field: 'firstName',

      verticalAlign,
    },
  };
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: '10px',
        }}
      >
        <p>Select the vertical align</p>
        <select
          value={verticalAlign}
          onChange={(e) =>
            setVerticalAlign(e.target.value as 'start' | 'center' | 'end')
          }
        >
          <option value="start">Start</option>
          <option value="center">Center</option>
          <option value="end">End</option>
        </select>
      </div>
      <DataSource<Developer> data={dataSource} primaryKey="id">
        <InfiniteTable<Developer>
          debugId="column-vertical-align-example"
          columns={columns}
          columnDefaultWidth={250}
          rowHeight={80}
          headerOptions={{
            alwaysReserveSpaceForSortIcon: false,
          }}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

### columns.headerAlign (`'start' | 'center' | 'end'`)

> Controls the alignment of the column header. See related [`columns.align`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.align) and [`columns.headerCssEllipsis`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerCssEllipsis).

**Example: Column header align example**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

export default function App() {
  const [headerAlign, setHeaderAlign] = React.useState<
    'start' | 'center' | 'end'
  >('start');

  const columns: InfiniteTablePropColumns<Developer> = {
    firstName: {
      field: 'firstName',

      headerAlign,
    },
  };
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: '10px',
        }}
      >
        <p>Select the header align</p>
        <select
          value={headerAlign}
          onChange={(e) =>
            setHeaderAlign(e.target.value as 'start' | 'center' | 'end')
          }
        >
          <option value="start">Start</option>
          <option value="center">Center</option>
          <option value="end">End</option>
        </select>
      </div>
      <DataSource<Developer> data={dataSource} primaryKey="id">
        <InfiniteTable<Developer>
          debugId="column-header-align-example"
          columns={columns}
          columnDefaultWidth={250}
          headerOptions={{
            alwaysReserveSpaceForSortIcon: false,
          }}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

### columnTypes.headerStyle (`CSSProperties | (args) => CSSProperties`)

> Controls styling for the column header for columns with this column type. Can be a style object or a function returning a style object.

See docs at [`columns.headerStyle`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.headerStyle).

### columnTypes.defaultWidth (`number`)

> Specifies a default fixed width for the column type. Will be overriden in any column that already specifies a `defaultWidth` property.

See related [`columnTypes.defaultFlex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.defaultFlex), [`columns.defaultWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultWidth) and [`columns.defaultFlex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultFlex)

### columnTypes.maxWidth (`number`)

> Specifies a default maximum width for the column type. Will be overriden in any column that already specifies a `maxWidth` property.

See related [`columnTypes.maxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.maxWidth) and [`columns.maxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.maxWidth)

### columnTypes.minWidth (`number`)

> Specifies a default minimum width for the column type. Will be overriden in any column that already specifies a `minWidth` property.

See related [`columnTypes.maxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes.maxWidth) and [`columns.minWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.minWidth)

### defaultActiveCellIndex (`[number,number]`)

> Specifies the active cell for keyboard navigation. This is an uncontrolled prop. See the [Keyboard Navigation](https://infinite-table.com/docs/learn/keyboard-navigation/navigating-cells.md) page for more details.

See [`activeCellIndex`](https://infinite-table.com/docs/reference/infinite-table-props.md#activeCellIndex) for the controlled version of this prop and
[`keyboardNavigation`](https://infinite-table.com/docs/reference/infinite-table-props.md#keyboardNavigation) for the keyboard navigation behavior.

**Example: Uncontrolled keyboard navigation for cells**

This example starts with cell `[2,0]` already active.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardNavigationForCells() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="navigating-cells-uncontrolled-example"
          defaultActiveCellIndex={[2, 0]}
          columns={columns}
        />
      </DataSource>
    </>
  );
}
```

### columnsTypes.sortable (`boolean`)

> Specifies the sorting behavior for columns of this type.

Overrides the global [`sortable`](https://infinite-table.com/docs/reference/infinite-table-props.md#sortable) prop, but is overriden by the column's own [sortable](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.sortable) property.

### defaultActiveRowIndex (`number`)

> Specifies the active row for keyboard navigation. This is an uncontrolled prop. See the [Keyboard Navigation](https://infinite-table.com/docs/learn/keyboard-navigation/navigating-rows.md) page for more details.

See [`activeRowIndex`](https://infinite-table.com/docs/reference/infinite-table-props.md#activeRowIndex) for the controlled version of this prop and
[`keyboardNavigation`](https://infinite-table.com/docs/reference/infinite-table-props.md#keyboardNavigation) for the keyboard navigation behavior.

**Example: Uncontrolled keyboard navigation for rows**

This example starts with row at index `2` already active.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

const domProps = { style: { height: '90vh' } };

export default function KeyboardNavigationForRows() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="navigating-rows-uncontrolled-example"
          domProps={domProps}
          columns={columns}
          keyboardNavigation="row"
          defaultActiveRowIndex={2}
        />
      </DataSource>
    </>
  );
}
```

### defaultColumnOrder (`string[]|true`)

> Defines the order in which columns are displayed in the component.

For controlled usage, see [`columnOrder`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnOrder).

When using this uncontrolled prop, you can also listen to [`onColumnOrderChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnOrderChange) to be notified of column order changes

The `defaultColumnOrder` array can contain identifiers that are not yet defined in the [`columns`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns) Map or can contain duplicate ids. This is a feature, not a bug. We want to allow you to use the `defaultColumnOrder` in a flexible way so it can define the order of current and future columns.

Displaying the same column twice is a perfectly valid use case.

See [Column Order](https://infinite-table.com/docs/learn/columns/column-order.md) for more details on ordering columns both programatically and via drag & drop.

**Example: Uncontrolled column order**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};

export const columns: InfiniteTablePropColumns<Employee> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
    columnGroup: 'location',
  },
  city: {
    field: 'city',
    header: 'City',
    columnGroup: 'address',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
  department: {
    field: 'department',
    header: 'Department',
  },
  team: {
    field: 'team',
    header: 'Team',
  },
  company: { field: 'companyName', header: 'Company' },

  companySize: {
    field: 'companySize',
    header: 'Company Size',
  },
};

const columnOrder = [
  'firstName',
  'notfound',
  'country',
  'team',
  'company',
  'firstName',
  'not existing',
  'companySize',
  'country',
];

export default function App() {
  return (
    <DataSource<Employee> data={dataSource} primaryKey="id">
      <InfiniteTable<Employee>
        debugId="defaultColumnOrder-example"
        columns={columns}
        defaultColumnOrder={columnOrder}
        columnDefaultWidth={200}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};
```

### defaultColumnSizing (`Record<string,{width,flex,...}>`)

> Defines a default sizing of columns in the grid.

This is an uncontrolled property. For the controlled version and more details, see [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing).

It is an object that maps column ids to column sizing options. The values in the objects can contain the following properties:

- [flex](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing.flex) - use this for flexible columns. Behaves like the `flex` CSS property.
- [width](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing.width) - use this for fixed sized columns
- [minWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing.minWidth) - specifies the minimum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.
- [maxWidth](https://infinite-table.com/docs/reference/infinite-table-props.md#defaultColumnSizing.maxWidth) - specifies the maximum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.

**Example: Uncontrolled column sizing**

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

const defaultColumnSizing: InfiniteTablePropColumnSizing = {
  country: { width: 100 },
  city: { flex: 1, maxWidth: 300 },
  salary: { flex: 2 },
};

export default function App() {
  return (
    <DataSource<Employee> data={dataSource} primaryKey="id">
      <InfiniteTable<Employee>
        debugId="defaultColumnSizing-example"
        columns={columns}
        columnDefaultWidth={50}
        defaultColumnSizing={defaultColumnSizing}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

For auto-sizing columns, see [`autoSizeColumnsKey`](https://infinite-table.com/docs/reference/infinite-table-props.md#autoSizeColumnsKey).

### defaultColumnSizing.flex (`number`)

> Specifies the flex value for the column.

See [`columnSizing.flex`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.flex) for details.

### defaultColumnSizing.minWidth (`number`)

> Specifies the minimum width for a column. Especially useful for flexible columns.

See [`columnSizing.minWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.minWidth) for details.

### defaultColumnSizing.maxWidth (`number`)

> Specifies the maximum width for a column. Especially useful for flexible columns.

See [`columnSizing.maxWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.maxWidth) for details.

### defaultColumnSizing.width (`number`)

> Specifies the fixed width for the column.

See [`columnSizing.width`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing.width) for details.

### domProps (`React.HTMLProps<HTMLDivElement>`)

> DOM properties to be applied to the component root element.

For applying a className when the component is focused, see [`focusedClassName`](https://infinite-table.com/docs/reference/infinite-table-props.md#focusedClassName)

For applying a className when the focus is within the component, see [`focusedWithinClassName`](https://infinite-table.com/docs/reference/infinite-table-props.md#focusedWithinClassName)

**Example**

```ts files=["domprops-example.page.tsx","data.ts"]

```

### editable (`(param) => boolean | Promise<boolean>`)

> Controls whether columns are editable or not.

This overrides both the global [`columnDefaultEditable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnDefaultEditable) prop and the column's own [defaultEditable](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.defaultEditable) property.

This function prop will be called when an edit is triggered on the column. The function will be called with a single object that contains the following properties:

- `value` - the current value of the cell (the value currently displayed, so after [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) and [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) have been applied)
- `rawValue` - the current value of the cell, but before any formatting and custom rendering has been applied. This is either the field value from the current data object, or the result of the column [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) function.
- `data` - the data object (of type `DATA_TYPE`) for the current row
- `rowInfo` - the row info object that underlies the row
- `column` - the current column on which editing is invoked
- `api` - a reference to the [InfiniteTable API](https://infinite-table.com/docs/reference/api/index.md)
- `dataSourceApi` - - a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)

The function can return a `boolean` value or a `Promise` that resolves to a `boolean` - this means you can asynchronously decide whether the cell is editable or not.

By default, double-clicking an editable cell will show the cell editor. You can prevent this by returning `{preventEdit: true}` from the [onCellDoubleClick](https://infinite-table.com/docs/reference/infinite-table-props.md#onCellDoubleClick) function prop.

### focusedClassName (`string`)

> CSS class name to be applied to the component root element when it has focus.

For applying a className when the focus is within the component, see [`focusedWithinClassName`](https://infinite-table.com/docs/reference/infinite-table-props.md#focusedWithinClassName)

For focus style, see [`focusedStyle`](https://infinite-table.com/docs/reference/infinite-table-props.md#focusedStyle).

### focusedWithinClassName (`string`)

> CSS class name to be applied to the component root element when there is focus within (inside) the component.

For applying a className when the component root element is focused, see [`focusedClassName`](https://infinite-table.com/docs/reference/infinite-table-props.md#focusedClassName)

### focusedStyle

> Specifies the `style` to be applied to the component root element when it has focus.

**Example: focusedStyle example**

```ts files=["focusedStyle-example.page.tsx","data.ts"]

```

### focusedWithinStyle

> Specifies the `style` to be applied to the component root element when there is focus within (inside) the component.

To listen to focusWithin changes, listen to [`onFocusWithin`](https://infinite-table.com/docs/reference/infinite-table-props.md#onFocusWithin) and [`onBlurWithin`](https://infinite-table.com/docs/reference/infinite-table-props.md#onBlurWithin).

**Example: focusedWithinStyle example - focus an input inside the table to see it in action**

```ts files=["focusedWithinStyle-example.page.tsx","data.ts"]

```

### getCellContextMenuItems (`({data, column, rowInfo}) => MenuItem[] | null | { items: MenuItem[], columns: [{name}] } | Promise`)

> Customises the context menu items for a cell.

If you want to customize the context menu even when the user clicks outside any cell, but inside the table body, use [`getContextMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getContextMenuItems).

The `getCellContextMenuItems` function can return one of the following:

- `null` - no custom context menu will be displayed, the default context menu will be shown (default event behavior not prevented)
- `[]` - an empty array - no custom context menu will be displayed, but the default context menu is not shown - the default event behavior is prevented
- `Array<MenuItem>` - an array of menu items to be displayed in the context menu - each `MenuItem` should have:
  - a unique `key` property,
  - a `label` property with the value to display in the menu cell - it's called `label` because this is the name of the default column in the context menu
  - an optional `onAction({ key, item, hideMenu: () => void })` callback function to handle the click action on the menu item.
  - an optional `onClick(event)` callback function to handle the click event on the menu item.
  - an optional `hideMenuOnAction: boolean` - if `true`, it will close the context menu when the menu item is clicked

**Example: Using context menus**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  stack: {
    field: 'stack',
    header: 'Stack',
  },
  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  age: {
    field: 'age',
    header: 'Age',
  },
  hobby: {
    field: 'hobby',
    header: 'Hobby',
  },
  preferredLanguage: {
    header: 'Language',
    field: 'preferredLanguage',
  },
};

export default function App() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="cell-basic-context-menu-example"
          columns={columns}
          getCellContextMenuItems={({ data, column }) => {
            return [
              {
                key: 'hello',
                label: `Hello, ${data?.lastName} ${data?.firstName}`,
                onClick: () => {
                  alert(`Hello, ${data?.lastName} ${data?.firstName}`);
                },
              },
              {
                key: 'col',
                label: `Current clicked column: ${column.header}`,
              },
              {
                key: 'learn',
                label: `Learn`,
                menu: {
                  items: [
                    {
                      key: 'backend',
                      label: 'Backend',
                      onClick: () => {
                        alert(
                          `Learn Backend, ${data?.lastName} ${data?.firstName}`,
                        );
                      },
                    },
                    {
                      key: 'frontend',
                      label: 'Frontend',
                      onClick: () => {
                        alert(
                          `Learn Frontend, ${data?.lastName} ${data?.firstName}`,
                        );
                      },
                    },
                  ],
                },
              },
            ];
          }}
        />
      </DataSource>
    </>
  );
}
```

This function can also return a `Promise` that resolves to one of the above types. This is useful for lazy loading the context menu items.

When returning a `Promise`, the context menu will be shown after the promise resolves, and the default browser context menu is not shown.

In addition, if you need to configure the context menu to have other columns rather than the default column (named `label`), you can do so by returning an object with `columns` and `items`:

```tsx
const getCellContextMenuItems = () => {
  return {
    columns: [{ name: 'label' }, { name: 'icon' }],
    items: [
      {
        label: 'Welcome',
        icon: '👋',
        key: 'hi',
      },
      {
        label: 'Convert',
        icon: '🔁',
        key: 'convert',
      },
    ],
  };
};
```

**Example: Customising columns in the context menu**

Right-click any cell in the table to see a context menu with multiple columns (`icon`, `label` and `description`).

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  stack: {
    field: 'stack',
    header: 'Stack',
  },
  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  age: {
    field: 'age',
    header: 'Age',
  },
  hobby: {
    field: 'hobby',
    header: 'Hobby',
  },
  preferredLanguage: {
    header: 'Language',
    field: 'preferredLanguage',
  },
};

export default function App() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="cells-with-custom-columns-context-menu-example"
          columns={columns}
          columnDefaultEditable
          getCellContextMenuItems={({ data, column }) => {
            const columns = [
              { name: 'icon' },
              { name: 'label' },
              { name: 'description' },
            ];
            return {
              columns,
              items: [
                {
                  key: 'hello',
                  icon: '👋',
                  label: `Hello, ${data?.lastName} ${data?.firstName}`,
                  description: `This is a description for ${data?.lastName}`,
                },
                {
                  key: 'col',
                  icon: '🙌',
                  label: `Column: ${column.header}`,
                  description: `Current clicked column: ${column.header}`,
                },
                {
                  key: 'learn',
                  icon: '📚',
                  label: `Learn`,
                  description: `Learn more about ${data?.preferredLanguage}`,
                  menu: {
                    columns,
                    items: [
                      {
                        key: 'backend',
                        label: 'Backend',
                        icon: '👨‍💻',
                        description: 'In the Backend',
                      },
                      {
                        key: 'frontend',
                        label: 'Frontend',
                        icon: '👨‍💻',
                        description: 'In the Frontend',
                      },
                    ],
                  },
                },
              ],
            };
          }}
        />
      </DataSource>
    </>
  );
}
```

### getContextMenuItems (`({event, data?, column?, rowInfo}, {api, dataSourceApi}) => MenuItem[] | null | { items: MenuItem[], columns: [{name}] } | Promise`)

> Customises the context menu items for the whole table.

If you want to customize the context menu only when the user clicks inside a cell, use [`getCellContextMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getCellContextMenuItems), which is probably what you're looking for.

The first argument this function is called with has the same shape as the one for [`getCellContextMenuItems`](https://infinite-table.com/docs/reference/infinite-table-props.md#getCellContextMenuItems) but all cell-related properties could also be `undefined`. Also, the `event` is available as a property on this object.

If this function returns null, the default context menu of the browser will be shown (default event behavior not prevented).

**Example: Using context menus for the whole table**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
    header: 'Name',
    defaultWidth: 120,
  },
  age: {
    field: 'age',
    header: 'Age',
    defaultWidth: 100,
  },
  preferredLanguage: {
    header: 'Language',
    defaultWidth: 120,
    field: 'preferredLanguage',
  },
};

export default function App() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="table-basic-context-menu-example"
          columns={columns}
          getContextMenuItems={({ data, column }) => {
            if (!data)
              return [
                {
                  key: 'add',
                  label: 'Add Item',
                  onClick: () => {
                    alert('Add Item');
                  },
                },
              ];

            return [
              {
                key: 'hello',
                label: `Hello, ${data?.lastName} ${data?.firstName}`,
                onClick: () => {
                  alert(`Hello, ${data?.lastName} ${data?.firstName}`);
                },
              },
              {
                key: 'col',
                label: `Current clicked column: ${column?.header}`,
              },
              {
                key: 'learn',
                label: `Learn`,
                menu: {
                  items: [
                    {
                      key: 'backend',
                      label: 'Backend',
                      onClick: () => {
                        alert(
                          `Learn Backend, ${data?.lastName} ${data?.firstName}`,
                        );
                      },
                    },
                    {
                      key: 'frontend',
                      label: 'Frontend',
                      onClick: () => {
                        alert(
                          `Learn Frontend, ${data?.lastName} ${data?.firstName}`,
                        );
                      },
                    },
                  ],
                },
              },
            ];
          }}
        />
      </DataSource>
    </>
  );
}
```

This function can also return a `Promise` that resolves to one of the above types. This is useful for lazy loading the context menu items.

When returning a `Promise`, the context menu will be shown after the promise resolves, and the default browser context menu is not shown.

### getColumnMenuItems (`(items, context) => MenuItem[]`)

> Allows customization of the context menu items for a column.

Use this function to customize the context menu for columns. The function is called with the following arguments:

- `items` - the default menu items for the column - you can return this array as is to use the default menu items (same as not providing this function prop) or you can customize the array or return a new one altogether.
- `context` - an object that gives you access to the column and the grid state
  - `context.column: InfiniteTableComputedColumn<T>` - the current column for which the context menu is being shown
  - `context.api` - a reference to the [api](./reference/api)

**Example: getColumnMenuItems example - custom menu item and icon**

In this example, the currency and preferredLanguage columns have a custom icon for triggering the column context menu.

In addition, the `preferredLanguage` column has a custom header that shows a button for triggering the column context menu.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  currency: {
    field: 'currency',

    // custom menu icon
    renderMenuIcon: () => <div>🌎</div>,
  },

  preferredLanguage: {
    field: 'preferredLanguage',
    defaultWidth: 350,
    header: ({ columnApi, renderLocation }) => {
      // if we're inside the column menu with all columns, return only the col name
      if (renderLocation === 'column-menu') {
        return 'Preferred Language';
      }

      // but for the real column header
      // return this custom content
      return (
        <>
          Preferred Language{' '}
          <button
            // we need to stop propagation so we don't trigger a sort when this button is clicked
            onPointerDown={(e) => e.stopPropagation()}
            onMouseDown={(e) => {
              // again, stop propagation so the menu is not closed automatically
              // so we can control it in the line below
              e.stopPropagation();

              columnApi.toggleContextMenu(e.target);
            }}
            style={{ border: '1px solid magenta', margin: 2 }}
          >
            Toggle menu
          </button>
        </>
      );
    },

    // custom menu icon
    renderMenuIcon: () => <div>🌎</div>,
  },
  salary: {
    field: 'salary',
    // hide the menu icon
    renderMenuIcon: false,
  },
  country: {
    field: 'country',
  },
  id: { field: 'id', defaultWidth: 80, renderMenuIcon: false },
  firstName: {
    field: 'firstName',
  },
};

export default function ColumnContextMenuItems() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="getColumnMenuItems-example"
          columnHeaderHeight={70}
          columns={columns}
          getColumnMenuItems={(items, { column }) => {
            if (column.id === 'firstName') {
              // you can adjust the default items for a specific column
              items.splice(0, 0, {
                key: 'firstName',
                label: 'First name menu item',
                onAction: () => {
                  console.log('Hey there!');
                },
              });
            }

            items.push(
              {
                key: 'hello',
                label: 'Hello World',
                onAction: () => {
                  alert('Hello World from column ' + column.id);
                },
              },
              {
                key: 'translate',
                label: 'Translate',
                menu: {
                  items: [
                    {
                      key: 'translateToEnglish',
                      label: 'English',
                      onAction: () => {
                        console.log('Translate to English');
                      },
                    },
                    {
                      key: 'translateToFrench',
                      label: 'French',
                      onAction: () => {
                        console.log('Translate to French');
                      },
                    },
                  ],
                },
              },
            );
            return items;
          }}
        />
      </DataSource>
    </>
  );
}
```

### groupColumn (`InfiniteTableColumn|(colInfo, toggleGroupRow) => InfiniteTableColumn`)

> Allows you to define a custom configuration for one or multiple group columns. When this prop is defined, it gets merged onto any values specified in the [`groupBy.column`](https://infinite-table.com/docs/reference/datasource-props/index.md#groupBy.column) property.

If this is an object and no explicit [`groupRenderStrategy`](https://infinite-table.com/docs/reference/infinite-table-props.md#groupRenderStrategy) is specified, the component is rendered as if you had [groupRenderStrategy="single-column"](https://infinite-table.com/docs/reference/infinite-table-props.md#groupRenderStrategy).

If it's a function, it will be called with the following arguments:

- `colInfo` - an object with the following properties:
- `colInfo.groupCount` - the count of row groups
- `colInfo.groupBy` - the array of row groups, used by the `DataSource` to do the grouping
- `colInfo.groupRenderStrategy` - the current [render strategy for groups](https://infinite-table.com/docs/reference/infinite-table-props.md#groupRenderStrategy).
- `colInfo.groupByForColumn` - the grouping object (one of the items in `colInfo.groupBy`) corresponding to the current column. Only defined when `groupRenderStrategy` is `multi-column`.
- `colInfo.groupIndexForColumn` - the index of `colInfo.groupByForColumn` in `colInfo.groupBy` - corresponding to the current column. Only defined when `groupRenderStrategy` is `multi-column`.
- `toggleGroupRow(groupKeys: any[])` - a function you can use to toggle a group row. Pass an array of keys - the path to the group row you want to toggle.

You can still use [`groupColumn`](https://infinite-table.com/docs/reference/infinite-table-props.md#groupColumn) as a function with single column group render strategy, but in this case, you have to be explicit and specify [groupRenderStrategy="single-column"](https://infinite-table.com/docs/reference/infinite-table-props.md#groupRenderStrategy).

**Example: groupColumn used as an object**

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
  DataSourcePropGroupBy,
  InfiniteTableColumn,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';

const groupBy: DataSourcePropGroupBy<Developer> = [
  {
    field: 'stack',
  },
  {
    field: 'preferredLanguage',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  country: {
    field: 'country',
  },
  firstName: {
    field: 'firstName',
    style: {
      color: 'orange',
    },
    renderValue: ({ value, rowInfo }) =>
      rowInfo.isGroupRow ? null : `${value}.`,
  },
  stack: {
    field: 'stack',
    style: {
      color: 'tomato',
    },
  },
  age: { field: 'age' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  canDesign: { field: 'canDesign' },
};

const groupColumn: InfiniteTableColumn<Developer> = {
  field: 'firstName',
  renderValue: ({ value }) => {
    return `First name: ${value}`;
  },
};

export default function App() {
  return (
    <DataSource<Developer> data={dataSource} primaryKey="id" groupBy={groupBy}>
      <InfiniteTable<Developer>
        debugId="group-column-custom-renderers-example"
        groupColumn={groupColumn}
        columns={columns}
        columnDefaultWidth={250}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

**Example: groupColumn used as a function**

This example shows how to use [`groupColumn`](https://infinite-table.com/docs/reference/infinite-table-props.md#groupColumn) as a function that allows you to customize all generated group columns in a single place.

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
  DataSourcePropGroupBy,
  InfiniteTableColumn,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';

const groupBy: DataSourcePropGroupBy<Developer> = [
  {
    field: 'stack',
  },
  {
    field: 'preferredLanguage',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  country: {
    field: 'country',
  },
  firstName: {
    field: 'firstName',
    style: {
      color: 'orange',
    },
    renderValue: ({ value, rowInfo }) =>
      rowInfo.isGroupRow ? null : `${value}.`,
  },
  stack: {
    field: 'stack',
    style: {
      color: 'tomato',
    },
  },
  age: { field: 'age' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  canDesign: { field: 'canDesign' },
};

const groupColumn: InfiniteTableColumn<Developer> = {
  field: 'firstName',
  renderValue: ({ value }) => {
    return `First name: ${value}`;
  },
};

export default function App() {
  return (
    <DataSource<Developer> data={dataSource} primaryKey="id" groupBy={groupBy}>
      <InfiniteTable<Developer>
        debugId="group-column-custom-renderers-example"
        groupColumn={groupColumn}
        columns={columns}
        columnDefaultWidth={250}
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

### groupRenderStrategy (`'single-column'|'multi-column'`)

> Determines how grouping is rendered - whether a single or multiple columns are generated.

**Example**

```ts files=["groupRenderStrategy-example.page.tsx","employee-columns.ts"]

```

### hideColumnWhenGrouped (`boolean`)

> Allows you to hide group columns bound to fields that are grouped by (fields mentioned in [groupBy.field](https://infinite-table.com/docs/reference/datasource-props/index.md#groupBy)).

**Example**

In this example, toggle the checkbox to see the `stack` and `preferredLanguage` columns hide/show as the value of `hideColumnWhenGrouped` changes.

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type {
  DataSourcePropGroupBy,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

const groupBy: DataSourcePropGroupBy<Developer> = [
  {
    field: 'stack',
  },
  {
    field: 'preferredLanguage',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  country: {
    field: 'country',
  },
  firstName: {
    field: 'firstName',
  },
  preferredLanguage: {
    field: 'preferredLanguage',
  },
  stack: {
    field: 'stack',
    style: {
      color: 'tomato',
    },
  },
  age: { field: 'age' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  canDesign: { field: 'canDesign' },
};

const groupColumn = {
  field: 'firstName' as keyof Developer,
};

const domProps = {
  style: { flex: 1 },
};
export default function App() {
  const [hideColumnWhenGrouped, setHidden] = useState(true);
  return (
    <div
      style={{
        display: 'flex',
        flex: 1,
        color: 'var(--infinite-cell-color)',
        flexFlow: 'column',
        background: 'var(--infinite-background)',
      }}
    >
      <div style={{ padding: 10 }}>
        <label>
          <input
            type="checkbox"
            checked={hideColumnWhenGrouped}
            onChange={() => {
              setHidden(!hideColumnWhenGrouped);
            }}
          />
          Hide Column When Grouped
        </label>
      </div>
      <DataSource<Developer>
        data={dataSource}
        primaryKey="id"
        groupBy={groupBy}
      >
        <InfiniteTable<Developer>
          debugId="hideColumnWhenGrouped-example"
          groupColumn={groupColumn}
          columns={columns}
          hideColumnWhenGrouped={hideColumnWhenGrouped}
          columnDefaultWidth={250}
          domProps={domProps}
        />
      </DataSource>
    </div>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

### hideEmptyGroupColumns (`boolean`)

> Allows you to hide group columns which don't render any information (this happens when all previous groups are collapsed).

**Example**

```ts files=["hideEmptyGroupColumns-example.page.tsx","employee-columns.ts"]

```

### keyboardNavigation (`'cell'|'row'|false`)

> Determines whether keyboard navigation is enabled.

Available values:

- `'cell'` - enables keyboard navigation for cells. This is the default.
- `'row'` - enables keyboard navigation for rows.
- `false` - disables keyboard navigation.

For cell keyboard navigation, see [`activeCellIndex`](https://infinite-table.com/docs/reference/infinite-table-props.md#activeCellIndex).
For row keyboard navigation, see [`activeRowIndex`](https://infinite-table.com/docs/reference/infinite-table-props.md#activeRowIndex).

**Example: Keyboard navigation**

This example starts with cell `[2,0]` already active.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + `/developers1k-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardNavigationForCells() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="navigating-cells-uncontrolled-example"
          defaultActiveCellIndex={[2, 0]}
          columns={columns}
        />
      </DataSource>
    </>
  );
}
```

**Example: Disabled Keyboard navigation**

In this example the keyboard navigation is disabled.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' +
      `/developers1k-sql?`,
  )
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardNavigationForRows() {
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="navigation-disabled-example"
          keyboardNavigation={false}
          columns={columns}
        />
      </DataSource>
    </>
  );
}
```

### keyboardSelection (`boolean`)

> Determines whether the keyboard can be used for selecting/deselecting rows/cells.

By default [`keyboardSelection`](https://infinite-table.com/docs/reference/infinite-table-props.md#keyboardSelection) is enabled, so you can use the keyboard **spacebar** key to select multiple rows. Using the spacebar key is equivalent to doing a mouse click, so expect the combination of **spacebar** + `cmd`/`ctrl`/`shift` modifier keys to behave just like clicking + the same modifier keys.

For specifying the selection mode, use [`selectionMode`](https://infinite-table.com/docs/reference/datasource-props/index.md#selectionMode)

**Example: Toggling keyboard navigation**

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

const columns: InfiniteTablePropColumns<Developer> = {
  country: {
    field: 'country',
  },
  firstName: {
    field: 'firstName',
  },
  stack: {
    field: 'stack',
  },
  age: { field: 'age' },
  id: { field: 'id' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  canDesign: { field: 'canDesign' },
};

export default function App() {
  const [keyboardSelection, setKeyboardSelection] = useState(true);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: 10,
        }}
      >
        Keyboard selection is now{' '}
        <b>{keyboardSelection ? 'enabled' : 'disabled'}</b>.
        <button
          style={{
            border: '1px solid tomato',
            display: 'block',
            padding: 5,
            marginTop: 10,
          }}
          onClick={() => setKeyboardSelection((x) => !x)}
        >
          Click to toggle keyboard selection
        </button>
      </div>
      <DataSource<Developer>
        data={dataSource}
        selectionMode="multi-row"
        primaryKey="id"
      >
        <InfiniteTable<Developer>
          debugId="default-selection-mode-multi-row-keyboard-toggle-example"
          keyboardSelection={keyboardSelection}
          columns={columns}
          columnDefaultWidth={150}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

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

### loadingText (`ReactNode`)

> The text inside the load mask - displayed when [loading=true](https://infinite-table.com/docs/reference/datasource-props/index.md#loading).

**Example: Customized loading text**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';

export default function App() {
  return (
    <DataSource<Employee> loading data={employees} primaryKey="id">
      <InfiniteTable<Employee>
        debugId="loadingText-example"
        loadingText="Please wait ..."
        columnDefaultWidth={130}
        columns={columns}
      />
    </DataSource>
  );
}

type Employee = {
  id: string | number;
  name: string;
  salary: number;
  department: string;
  company: string;
};

const employees: Employee[] = [
  {
    id: 1,
    name: 'Bob',
    salary: 10_000,
    department: 'IT',
    company: 'Bobsons',
  },
  {
    id: 2,
    name: 'Alice',
    salary: 20_000,
    department: 'IT',
    company: 'Bobsons',
  },
];

const columns: Record<string, InfiniteTableColumn<Employee>> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 80,
  },
  name: {
    field: 'name',
  },
  salary: { field: 'salary', type: 'number' },
  department: { field: 'department', header: 'Dep.' },
  company: { field: 'company' },
};
```

### multiSortBehavior (`'append'|'replace'`)

> Specifies the behavior of the DataGrid when [multiple sorting](https://infinite-table.com/docs/learn/sorting/multiple-sorting.md) is configured. Defaults to `'replace'`.

When `InfiniteTable` is configured with multiple sorting there are two supported behaviors:

- `append` - when this behavior is used, clicking a column header adds that column to the alredy existing sort. If the column is already sorted, the sort direction is reversed. In order to remove a column from the sort, the user needs to click the column header in order to toggle sorting from ascending to descending and then to no sorting.
- `replace` - the default behavior - a user clicking a column header removes any existing sorting and sets that column as sorted. In order to add a new column to the sort, the user needs to hold the `Ctrl/Cmd` key while clicking the column header.

**Example**

Try clicking the `age` column and then the `firstName` column.

If the multi-sort behavior is `replace`, clicking the second column will remove the sort from the first column.
In order for the sorting to be additive, even if the behavior is `replace`, use the `Ctrl`/`Cmd` key while clicking the column header.

If the multi-sort behavior is `append`, clicking the second column will add it to the sort.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropMultiSortBehavior,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

type Developer = {
  id: number;
  firstName: string;
  country: string;
  city: string;
  currency: string;
  email: string;
  preferredLanguage: string;
  hobby: string;
  salary: number;
  age: number;
};

const columns: InfiniteTablePropColumns<Developer> = {
  firstName: { field: 'firstName', header: 'First Name' },
  age: { field: 'age', header: 'Age' },
  salary: {
    field: 'salary',
    header: 'Salary',
    type: 'number',
  },
  country: { field: 'country', header: 'Country' },
  preferredLanguage: { field: 'preferredLanguage' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function LocalUncontrolledSingleSortingExample() {
  const [multiSortBehavior, setMultiSortBehavior] = React.useState<
    'append' | 'replace'
  >('replace');
  return (
    <>
      <div
        style={{
          background: 'var(--infinite-background)',
          color: 'var(--infinite-cell-color)',
          display: 'flex',
          flexDirection: 'row',
        }}
      >
        <p style={{ padding: 10 }}>Select the multi-sort behavior</p>
        <select
          style={{
            margin: '10px 0',
            display: 'inline-block',
            background: 'var(--infinite-background)',
            color: 'var(--infinite-cell-color)',
            padding: 'var(--infinite-space-3)',
          }}
          value={multiSortBehavior}
          onChange={(event) => {
            const multiSortBehavior = event.target
              .value as InfiniteTablePropMultiSortBehavior;

            setMultiSortBehavior(multiSortBehavior);
          }}
        >
          <option value="replace">replace</option>
          <option value="append">append</option>
        </select>
      </div>
      <DataSource<Developer>
        primaryKey="id"
        data={dataSource}
        defaultSortInfo={[]}
      >
        <InfiniteTable<Developer>
          debugId="local-multi-sorting-example-defaults-with-local-data"
          columns={columns}
          columnDefaultWidth={120}
          multiSortBehavior={multiSortBehavior}
        />
      </DataSource>
    </>
  );
}

const dataSource: Developer[] = [
  {
    id: 0,
    firstName: 'Nya',
    country: 'India',
    city: 'Unnao',
    age: 24,
    currency: 'JPY',
    preferredLanguage: 'TypeScript',
    salary: 60000,
    hobby: 'sports',
    email: 'Nya44@gmail.com',
  },
  {
    id: 1,
    firstName: 'Axel',
    country: 'Mexico',
    city: 'Cuitlahuac',
    age: 46,
    currency: 'USD',
    preferredLanguage: 'TypeScript',
    salary: 100000,
    hobby: 'sports',
    email: 'Axel93@hotmail.com',
  },
  {
    id: 2,
    firstName: 'Gonzalo',
    country: 'United Arab Emirates',
    city: 'Fujairah',
    age: 24,
    currency: 'JPY',
    preferredLanguage: 'Go',
    salary: 120000,
    hobby: 'photography',
    email: 'Gonzalo_McGlynn34@gmail.com',
  },
  {
    id: 3,
    firstName: 'Sherwood',
    country: 'Mexico',
    city: 'Tlacolula de Matamoros',
    age: 24,
    currency: 'CHF',
    preferredLanguage: 'Rust',
    salary: 99000,
    hobby: 'cooking',
    email: 'Sherwood_McLaughlin65@hotmail.com',
  },
  {
    id: 4,
    firstName: 'Alexandre',
    country: 'France',
    city: 'Persan',
    age: 24,
    currency: 'EUR',
    preferredLanguage: 'Go',
    salary: 97000,
    hobby: 'reading',
    email: 'Alexandre_Harber@hotmail.com',
  },
  {
    id: 5,
    firstName: 'Mariane',
    country: 'United States',
    city: 'Hays',
    age: 23,
    currency: 'EUR',
    preferredLanguage: 'TypeScript',

    salary: 58000,
    hobby: 'cooking',
    email: 'Mariane0@hotmail.com',
  },
  {
    id: 6,
    firstName: 'Rosalind',
    country: 'Mexico',
    city: 'Nuevo Casas Grandes',
    age: 23,
    currency: 'AUD',
    preferredLanguage: 'JavaScript',
    salary: 198000,
    hobby: 'dancing',
    email: 'Rosalind69@gmail.com',
  },
  {
    id: 7,
    firstName: 'Lolita',
    country: 'Sweden',
    city: 'Delsbo',
    age: 22,
    currency: 'JPY',
    preferredLanguage: 'TypeScript',
    salary: 200000,
    hobby: 'cooking',
    email: 'Lolita.Hayes@hotmail.com',
  },
  {
    id: 8,
    firstName: 'Tre',
    country: 'Germany',
    city: 'Bad Camberg',
    age: 23,
    currency: 'GBP',
    preferredLanguage: 'TypeScript',
    salary: 200000,
    hobby: 'sports',
    email: 'Tre28@gmail.com',
  },
  {
    id: 9,
    firstName: 'Lurline',
    country: 'Canada',
    city: 'Raymore',
    age: 23,
    currency: 'EUR',
    preferredLanguage: 'Rust',
    salary: 58000,
    hobby: 'sports',
    email: 'Lurline_Deckow@gmail.com',
  },
];
```

### onActiveCellIndexChange (`(activeCellIndex:[number,number])=>void`)

> Callback triggered by cell navigation. See the [Keyboard Navigation](https://infinite-table.com/docs/learn/keyboard-navigation/navigating-cells.md) page for more details.

See related [`activeCellIndex`](https://infinite-table.com/docs/reference/infinite-table-props.md#activeCellIndex) and [`keyboardNavigation`](https://infinite-table.com/docs/reference/infinite-table-props.md#keyboardNavigation) for the keyboard navigation behavior.

**Example: Controlled keyboard navigation (for cells) with callback**

This example uses `onActiveCellIndexChange` to react to changes in the `activeCellIndex`.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' +
      `/developers1k-sql?`,
  )
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardNavigationForCells() {
  const [activeCellIndex, setActiveCellIndex] = React.useState<
    [number, number]
  >([2, 0]);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
        }}
      >
        Current active cell: {activeCellIndex[0]}, {activeCellIndex[1]}.
      </div>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="navigating-cells-controlled-example"
          activeCellIndex={activeCellIndex}
          onActiveCellIndexChange={setActiveCellIndex}
          columns={columns}
        />
      </DataSource>
    </>
  );
}
```

### onActiveRowIndexChange (`(activeRowIndex:number)=>void`)

> Callback triggered by row navigation. See the [Keyboard Navigation](https://infinite-table.com/docs/learn/keyboard-navigation/navigating-rows.md) page for more details.

See related [`activeRowIndex`](https://infinite-table.com/docs/reference/infinite-table-props.md#activeRowIndex) and [`keyboardNavigation`](https://infinite-table.com/docs/reference/infinite-table-props.md#keyboardNavigation) for the keyboard navigation behavior.

**Example: Controlled keyboard navigation (for rows) with callback**

This example uses `onActiveRowIndexChange` to react to changes in the `activeRowIndex`.

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource: DataSourceData<Developer> = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' +
      `/developers1k-sql?`,
  )
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function KeyboardNavigationForRows() {
  const [activeRowIndex, setActiveRowIndex] = React.useState(2);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
        }}
      >
        Current active row: {activeRowIndex}.
      </div>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="navigating-rows-controlled-example"
          keyboardNavigation="row"
          activeRowIndex={activeRowIndex}
          onActiveRowIndexChange={setActiveRowIndex}
          columns={columns}
        />
      </DataSource>
    </>
  );
}
```

### onBlurWithin (`(event)=> void`)

> Function that is called when a focused element is blurred within the component.

For the corresponding focus event, see [`onFocusWithin`](https://infinite-table.com/docs/reference/infinite-table-props.md#onFocusWithin)

This callback is fired when a focusable element inside the component is blurred, and the focus is no longer within the component. In other words, when you navigate focusable elements inside the table, this callback is not fired.

**Example: Blur an input inside the table to see the callback fired**

```ts files=["onBlurWithin-example.page.tsx","data.ts"]

```

### onCellDoubleClick (`({ colIndex, rowIndex, column, columnApi, api, dataSourceApi }, event) => void | {preventEdit?: boolean} `)

> Callback function called when a cell has been double clicked.

If the cell is editable, you can prevent going into edit mode by returning `{preventEdit: true}` from the function.

### onCellClick (`({ colIndex, rowIndex, column, columnApi, api, dataSourceApi }, event) => void`)

> Callback function called when a cell has been clicked.

The first argument of the function is an object that contains the following properties:

- `rowIndex: number` - the index of the row that was clicked.
- `colIndex: number` - the index of the column that was clicked. This index is the index in the array of visible columns.
- `column: InfiniteTableComputedColumn<DATA_TYPE>` - the column that has been clicked
- `columnApi: InfiniteTableColumnApi<DATA_TYPE>` - the [column API](https://infinite-table.com/docs/reference/column-api/index.md)
- `api: InfiniteTableApi<DATA_TYPE>` - a reference to the [API](docs/reference/api)
- `dataSourceApi: DataSourceApi<DATA_TYPE>` - a reference to the [Data Source API](https://infinite-table.com/docs/reference/datasource-api/index.md). Can be used to get the current data.

The second argument is the original browser click event.

### onColumnOrderChange (`(columnOrder: string[])=>void`)

> Called as a result of user changing the column order

### onColumnSizingChange (`(columnSizing)=>void`)

> Called as a result of user doing a column resize.

Use this callback to get updated information after a column resize is performed.

This works well in combination with the controlled [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) prop (though you don't have to use controlled [`columnSizing`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnSizing) in order to use this callback). For more info on resizing columns, see [Column Sizing](https://infinite-table.com/docs/learn/columns/fixed-and-flexible-size.md).

See related [`onViewportReservedWidthChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onViewportReservedWidthChange)

**Example: Controlled column sizing example with onColumnSizingChange**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumnGroup,
  InfiniteTablePropColumnSizing,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  currency: {
    field: 'currency',
    columnGroup: 'finance',
  },
  salary: {
    field: 'salary',
    columnGroup: 'finance',
  },
  country: {
    field: 'country',
    columnGroup: 'regionalInfo',
    maxWidth: 400,
  },
  preferredLanguage: {
    field: 'preferredLanguage',
    columnGroup: 'regionalInfo',
  },
  id: { field: 'id' },
  firstName: {
    field: 'firstName',
  },
  stack: {
    field: 'stack',
  },
};

const columnGrous: Record<string, InfiniteTableColumnGroup> = {
  regionalInfo: {
    header: 'Regional Info',
  },
  finance: {
    header: 'Finance',
    columnGroup: 'regionalInfo',
  },
};

export default function ColumnValueGetterExample() {
  const [columnSizing, setColumnSizing] =
    useState<InfiniteTablePropColumnSizing>({
      salary: {
        maxWidth: 130,
        width: 80,
      },
      currency: {
        maxWidth: 130,
        width: 80,
      },
    });
  return (
    <>
      <p style={{ color: 'var(--infinite-cell-color)' }}>
        Current column sizing:{' '}
        <code>
          <pre>{JSON.stringify(columnSizing, null, 2)}</pre>
        </code>
      </p>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="onColumnSizingChange-example"
          columnSizing={columnSizing}
          onColumnSizingChange={setColumnSizing}
          columnGroups={columnGrous}
          columns={columns}
          columnDefaultWidth={100}
        />
      </DataSource>
    </>
  );
}
```

### onEditAccepted (`({value, initialValue, column, rowInfo, ...}) => void`)

> Callback prop called when an edit is accepted

In order to decide whether an edit should be accepted or not, you can use the global [`shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#shouldAcceptEdit) prop or the column-level [column.shouldAcceptEdit](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit) alternative.

When neither the global [`shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#shouldAcceptEdit) nor the column-level [column.shouldAcceptEdit](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit) are defined, all edits are accepted by default.

This callback is called with a single object that has the following properties:

- `value` - the value that was accepted for the edit operation.
- `initialValue` - the initial value of the cell (the value before editing started)
- `rowInfo` - of type [`InfiniteTableRowInfo`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) - the row info object that underlies the row
- `api` - a reference to the [InfiniteTable API](https://infinite-table.com/docs/reference/api/index.md)
- `dataSouceApi` - a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)
- `column` - the column on which the edit was performed
- `columnApi` - a reference to the [column API](https://infinite-table.com/docs/reference/column-api/index.md)

See related [`onEditRejected`](https://infinite-table.com/docs/reference/infinite-table-props.md#onEditRejected) callback prop.

### onEditPersistSuccess (`({value, initialValue, column, rowInfo, ...})=>void`)

> Callback prop called when an edit is persisted successfully

Has the same signature as [`onEditAccepted`](https://infinite-table.com/docs/reference/infinite-table-props.md#onEditAccepted)

### onEditRejected (`({ value, initialValue, column, rowInfo, ... }) => void`)

> Callback prop called when an edit is rejected

In order to decide whether an edit should be accepted or rejected, you can use the global [`shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#shouldAcceptEdit) prop or the column-level [column.shouldAcceptEdit](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit) alternative.

When neither the global [`shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#shouldAcceptEdit) nor the column-level [column.shouldAcceptEdit](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit) are defined, all edits are accepted by default.

This callback prop has almost the same signature as the [`onEditAccepted`](https://infinite-table.com/docs/reference/infinite-table-props.md#onEditAccepted) callback prop. The only difference is that the argument passed to the function also contains an `error` property, with a reference to the error that caused the edit to be rejected.

### onFocusWithin (`(event)=> void`)

> Function that is called when the table receives focus within the component.

For the corresponding blur event, see [`onBlurWithin`](https://infinite-table.com/docs/reference/infinite-table-props.md#onBlurWithin)

**Example: Focus an input inside the table to see the callback fired**

```ts files=["onFocusWithin-example.page.tsx","data.ts"]

```

### onKeyDown (`({ api, dataSourceApi }, event) => void | InfiniteTablePropOnKeyDownResult`)

> Callback function called when the `keydown` event occurs on the table.

The first argument of the function is an object that contains the following properties:

- `api: InfiniteTableApi<DATA_TYPE>` - a reference to the [API](docs/reference/api)
- `dataSourceApi: DataSourceApi<DATA_TYPE>` - a reference to the [Data Source API](https://infinite-table.com/docs/reference/datasource-api/index.md). Can be used to get the current data.

The second argument is the original browser `keydown` event.

If you want to prevent some default behaviours, you can return an object with the following properties:

- `preventEdit: boolean` - if true, the cell editor will not be shown when hitting the `Enter` key in an editable cell.
- `preventEditStop: boolean` - if true, hitting the `Escape` key will not stop the edit.
- `preventSelection: boolean` - if true, the ` ` and `Cmd+a` keys will not select cells/rows
- `preventNavigation: boolean` - if true, keyboard navigation will be prevented when using `arrow` keys, `page up/down`, `home/end`, `enter`.

For keyboard shortcuts, see [`keyboardShortcuts`](https://infinite-table.com/docs/reference/infinite-table-props.md#keyboardShortcuts).

### onReady (`({api, dataSourceApi}) => void}`)

> Callback prop that is being called when the table is ready.

This is called only once with an object that has an `api` property, which is an instance of [`InfiniteTableApi<DATA_TYPE>`](https://infinite-table.com/docs/reference/api/index.md) and a `dataSourceApi` property, which is an instance of [`DataSourceApi<DATA_TYPE>`](https://infinite-table.com/docs/reference/datasource-api/index.md).

The `ready` state for the table means it has been layout out and has measured its available size for laying out the columns.

It will never be called again after the component is ready.

### onRenderRangeChange (`(range)=>void`)
> Called whenever the render range changes, that is, additional rows or columns come into view.

The first (and only) argument is an object with `{start, end}` where both `start` and `end` are arrays of `[rowIndex, colIndex]` pairs.

 So if you want to get the start and end indexes, you can do

 ```ts
 const [startRow, startCol] = renderRange.start;
 const [endRow, endCol] = renderRange.end;
 ```

This callback is not debounced or throttled, so it can be called multiple times in a short period of time, especially while scrolling. Make sure your function is fast, or attach a debounced function, in order to avoid performance issues.

```tsx
import {
  debounce,
  InfiniteTable,
  DataSource
} from '@infinite-table/infinite-react';

function App() {
  const onRenderRangeChange = useMemo(() => {
    return debounce((range) => {
      console.log(range.start, range.end);
    }, {wait: 100});
  }, []);

  return <DataSource<Developer>
    primaryKey="id"
    data={/*data*/}
  >
    <InfiniteTable<Developer>
      onRenderRangeChange={onRenderRangeChange}
      columns={/*columns*/}
    />
  </DataSource>
}
```

Unlike [`onScrollStop`](https://infinite-table.com/docs/reference/infinite-table-props.md#onScrollStop), this function is also called when the DataGrid is resized and also when initially rendered.

### onScrollStop (`({renderRange, viewportSize, scrollTop, scrollLeft})=>void`)

> Triggered when the user has stopped scrolling (after [`scrollStopDelay`](https://infinite-table.com/docs/reference/infinite-table-props.md#scrollStopDelay) milliseconds).

This is called when the user stops scrolling for a period of time - as configured by [`scrollStopDelay`](https://infinite-table.com/docs/reference/infinite-table-props.md#scrollStopDelay) (milliseconds).

The function is called with an object that has the following properties:
 - `renderRange` - the render range of the viewport. This is an object with `{start, end}` where both `start` and `end` are arrays of `[rowIndex, colIndex]` pairs.
 So if you want to get the start and end indexes, you can do
 ```ts
 const [startRow, startCol] = renderRange.start;
 const [endRow, endCol] = renderRange.end;
 ```

 - `viewportSize` - the size of the viewport - `{width, height}`
 - `scrollTop` - the scrollTop position of the viewport - `number`
 - `scrollLeft` - the scrollLeft position of the viewport - `number`

Also see [`onScrollToTop`](https://infinite-table.com/docs/reference/infinite-table-props.md#onScrollToTop), [`onScrollToBottom`](https://infinite-table.com/docs/reference/infinite-table-props.md#onScrollToBottom) and [`onRenderRangeChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onRenderRangeChange).

**Example: onScrollStop is called with viewport info - scroll the grid and see the console**

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTableProps,
} from '@infinite-table/infinite-react';

import type {
  InfiniteTablePropColumns,
  ScrollStopInfo,
} from '@infinite-table/infinite-react';
import * as React from 'react';

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;
  monthlyBonus: number;
  birthDate: Date;
  age: number;
};

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id' },
  firstName: { field: 'firstName' },
  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },
  country: { field: 'country' },
  age: { field: 'age', type: 'number' },
  salary: { field: 'salary', type: 'number' },
  currency: { field: 'currency', type: 'number' },
  birthDate: { field: 'birthDate', type: 'number' },
};

export default function App() {
  const onScrollStop: InfiniteTableProps<Developer>['onScrollStop'] =
    React.useCallback(
      ({
        renderRange,
        viewportSize,
        scrollTop,
        scrollLeft,
      }: ScrollStopInfo) => {
        console.log({ renderRange, viewportSize, scrollTop, scrollLeft });
      },
      [],
    );
  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="onScrollStop-example"
          onScrollStop={onScrollStop}
          columns={columns}
          columnDefaultWidth={250}
        />
      </DataSource>
    </>
  );
}
```

### onScrollToBottom (`()=>void`)

> Triggered when the user has scrolled to the bottom of the component. Also see [`onScrollToTop`](https://infinite-table.com/docs/reference/infinite-table-props.md#onScrollToTop) and [`onScrollStop`](https://infinite-table.com/docs/reference/infinite-table-props.md#onScrollStop).

Also see [`onScrollToTop`](https://infinite-table.com/docs/reference/infinite-table-props.md#onScrollToTop) and [`onScrollStop`](https://infinite-table.com/docs/reference/infinite-table-props.md#onScrollStop).

As an example usage, we're demoing live pagination, done in combination with the [react-query](https://tanstack.com/query/latest) library.

If you want to scroll to the top of the table, you can use the [`scrollTopKey`](https://infinite-table.com/docs/reference/infinite-table-props.md#scrollTopKey) prop.

**Example: Fetch new data on scroll to bottom**

```ts
import '@infinite-table/infinite-react/index.css';
import {
  InfiniteTable,
  InfiniteTableColumn,
  DataSource,
  DataSourceSingleSortInfo,
  DataSourceDataParams,
  DataSourceLivePaginationCursorFn,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useCallback } from 'react';
import {
  QueryClient,
  QueryClientProvider,
  useInfiniteQuery,
  keepPreviousData,
} from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
    },
  },
});

const emptyArray: Employee[] = [];

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  id: { field: 'id' },
  country: {
    field: 'country',
  },
  city: { field: 'city' },
  team: { field: 'team' },
  department: { field: 'department' },
  firstName: { field: 'firstName' },
  lastName: { field: 'lastName' },

  salary: { field: 'salary' },
  age: { field: 'age' },
};

type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: number;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};

const PAGE_SIZE = 10;

const dataSource = ({
  sortInfo,
  livePaginationCursor = 0,
}: {
  sortInfo: DataSourceSingleSortInfo<Employee> | null;
  livePaginationCursor: number;
}) => {
  return fetch(
    process.env.NEXT_PUBLIC_BASE_URL +
      `/employees10k?_limit=${PAGE_SIZE}&_sort=${sortInfo?.field}&_order=${
        sortInfo?.dir === 1 ? 'asc' : 'desc'
      }&_start=${livePaginationCursor}`,
  )
    .then(async (r) => {
      const data = await r.json();
      // we need the remote count, so we take it from headers
      const total = Number(r.headers.get('X-Total-Count')!);
      return { data, total };
    })
    .then(({ data, total }: { data: Employee[]; total: number }) => {
      const page = livePaginationCursor / PAGE_SIZE + 1;

      const prevPageCursor = Math.max(PAGE_SIZE * (page - 1), 0);
      return {
        data,
        hasMore: total > PAGE_SIZE * page,
        page,
        prevPageCursor,
        nextPageCursor: prevPageCursor + data.length,
      };
    })
    .then(
      (
        response,
      ): Promise<{
        data: Employee[];
        hasMore: boolean;
        page: number;
        nextPageCursor: number;
        prevPageCursor: number;
      }> => {
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve(response);
          }, 150);
        });
      },
    );
};

const Example = () => {
  const [dataParams, setDataParams] = React.useState<
    Partial<DataSourceDataParams<Employee>>
  >({
    groupBy: [],
    sortInfo: undefined,
    livePaginationCursor: null,
  });
  const {
    data,
    fetchNextPage: fetchNext,
    isFetchingNextPage,
  } = useInfiniteQuery({
    initialPageParam: 0,
    queryKey: ['employees', dataParams.sortInfo, dataParams.groupBy],
    queryFn: ({ pageParam = 0 }) => {
      const params = {
        livePaginationCursor: pageParam,
        sortInfo:
          dataParams.sortInfo as DataSourceSingleSortInfo<Employee> | null,
      };

      return dataSource(params);
    },

    placeholderData: keepPreviousData,
    getPreviousPageParam: (firstPage) => firstPage.prevPageCursor || 0,
    getNextPageParam: (lastPage) => {
      const nextPageCursor = lastPage.hasMore
        ? lastPage.nextPageCursor
        : undefined;

      return nextPageCursor;
    },

    select: (data) => {
      const flatData = data.pages.flatMap((x) => x.data);
      const nextPageCursor = data.pages[data.pages.length - 1].nextPageCursor;

      const result = {
        pages: flatData,
        pageParams: [nextPageCursor],
      };

      return result;
    },
  });

  const onDataParamsChange = useCallback(
    (dataParams: DataSourceDataParams<Employee>) => {
      const params = {
        groupBy: dataParams.groupBy,
        sortInfo: dataParams.sortInfo,
        livePaginationCursor: dataParams.livePaginationCursor,
      };

      setDataParams(params);
    },
    [],
  );

  const [scrollTopId, setScrollTop] = React.useState(0);

  React.useEffect(() => {
    // when sorting changes, scroll to the top
    setScrollTop(Date.now());
  }, [dataParams.sortInfo]);

  const fetchNextPage = () => {
    if (isFetchingNextPage) {
      return;
    }

    fetchNext();
  };

  React.useEffect(() => {
    fetchNextPage();
  }, [dataParams.livePaginationCursor]);

  const livePaginationCursorFn: DataSourceLivePaginationCursorFn<Employee> =
    useCallback(({ length }) => {
      return length;
    }, []);

  return (
    <React.StrictMode>
      <DataSource<Employee>
        primaryKey="id"
        // take the data from `data.pages`,
        // as returned from our react-query select function

        data={data?.pages || emptyArray}
        loading={isFetchingNextPage}
        onDataParamsChange={onDataParamsChange}
        livePagination
        livePaginationCursor={livePaginationCursorFn}
      >
        <InfiniteTable<Employee>
          debugId="live-pagination-example"
          scrollTopKey={scrollTopId}
          columnDefaultWidth={200}
          columns={columns}
        />
      </DataSource>
    </React.StrictMode>
  );
};

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  );
}

export default App;
```

### onViewportReservedWidthChange (`(reserved: number) => void`)

> Callback to be notified of changes to [`viewportReservedWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#viewportReservedWidth)

See [`viewportReservedWidth`](https://infinite-table.com/docs/reference/infinite-table-props.md#viewportReservedWidth) for details. See related [`onColumnSizingChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onColumnSizingChange).

When he user is performing a column resize (via drag & drop), [`onViewportReservedWidthChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onViewportReservedWidthChange) is called when the resize is finished (not the case for resizing with the **SHIFT** key pressed, when adjacent columns share the space between them since the reserved width is preserved).

**Example: Using onViewportReservedWidth to respond to user column resizing**

Resize a column to see `viewportReservedWidth` updated and then click the button to reset it to `0px`

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

const defaultColumnSizing: InfiniteTablePropColumnSizing = {
  country: { flex: 1 },
  city: { flex: 1 },
  salary: { flex: 2 },
};

export default function App() {
  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);
  return (
    <>
      <div style={{ color: 'var(--infinite-cell-color' }}>
        <p>Current viewport reserved width: {viewportReservedWidth}px.</p>

        <button
          style={{
            padding: 5,
            margin: 5,
            border: '2px solid currentColor',
          }}
          onClick={() => {
            setViewportReservedWidth(0);
          }}
        >
          Click to reset viewportReservedWidth to 0
        </button>
      </div>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="viewportReservedWidth-example"
          columns={columns}
          columnDefaultWidth={50}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
          defaultColumnSizing={defaultColumnSizing}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### persistEdit (`(params) => any|Error|Promise<any|Error>`)

> Custom function to persist an edit

This allows edits that have been accepted (see [`shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#shouldAcceptEdit)) to be persisted to a remote (or local) location.

This function is called with an object that has the following properties:

- `value` - the value that was accepted for the edit operation.
- `initialValue` - the initial value of the cell (the value that was displayed before editing started). This is the value resulting after [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) and [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) have been applied)
- `rawValue` - the initial value of the cell, but before any formatting and custom rendering has been applied. This is either the field value from the current data object, or the result of the column [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) function.
- `data` - the current data object
- `rowInfo` - the row info object that underlies the row
- `column` - the current column on which editing is invoked
- `api` - a reference to the [InfiniteTable API](https://infinite-table.com/docs/reference/api/index.md)
- `dataSourceApi` - - a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)

This function can be synchronous or asynchronous. For synchronous persisting, return an `Error` if the persisting fails, or any other value if all went well.

For asynchronous persisting, you have to return a `Promise`. If the persisting fails, resolve the promise with an `Error` object or reject the promise. If the persisting succeeded, resolve the promise with any non-error value.

### pivotGrandTotalColumnPosition

> Controls the position and visibility of pivot grand-total columns

If specified as `false`, the pivot grand-total columns are not displayed.

For normal pivot total columns, see [`pivotTotalColumnPosition`](https://infinite-table.com/docs/reference/infinite-table-props.md#pivotTotalColumnPosition).

Pivot total columns only display when [`pivotBy`](https://infinite-table.com/docs/reference/datasource-props/index.md#pivotBy) has two or more fields (`pivotBy.length > 1`). With a single pivot field, enabling [`pivotTotalColumnPosition`](https://infinite-table.com/docs/reference/infinite-table-props.md#pivotTotalColumnPosition) has no effect — the totals would be the same as the already displayed values. The example below pivots by `stack` and `canDesign` so both pivot totals and grand totals are visible.

**Example: Pivoting with pivotGrandTotalColumnPosition=start**

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceGroupBy,
  DataSourcePivotBy,
  InfiniteTableColumnAggregator,
  DataSourcePropAggregationReducers,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },
};

const defaultGroupBy: DataSourceGroupBy<Developer>[] = [
  {
    field: 'country',
  },
  {
    field: 'city',
  },
];

const defaultPivotBy: DataSourcePivotBy<Developer>[] = [
  {
    field: 'stack',
  },
  {
    field: 'canDesign',
    columnGroup: ({ columnGroup }) => {
      return {
        ...columnGroup,
        header:
          columnGroup.pivotGroupKey === 'yes' ? 'Designer' : 'Non-designer',
      };
    },
  },
];

const avgReducer: InfiniteTableColumnAggregator<Developer, any> = {
  initialValue: 0,

  reducer: (acc, sum) => acc + sum,
  done: (sum, arr) => Math.round(arr.length ? sum / arr.length : 0),
};

const aggregations: DataSourcePropAggregationReducers<Developer> = {
  salary: {
    ...avgReducer,
    name: 'Salary (avg)',
    field: 'salary',
  },
  age: {
    ...avgReducer,
    name: 'Age (avg)',
    field: 'age',
  },
};

export default function ColumnValueGetterExample() {
  return (
    <>
      <DataSource<Developer>
        primaryKey="id"
        defaultGroupBy={defaultGroupBy}
        defaultPivotBy={defaultPivotBy}
        aggregationReducers={aggregations}
        data={dataSource}
      >
        {({ pivotColumns, pivotColumnGroups }) => {
          return (
            <InfiniteTable<Developer>
              debugId="pivot-grand-total-column-position-example"
              groupRenderStrategy="single-column"
              columns={columns}
              columnDefaultWidth={200}
              pivotColumns={pivotColumns}
              pivotColumnGroups={pivotColumnGroups}
              pivotTotalColumnPosition="end"
              pivotGrandTotalColumnPosition="start"
            />
          );
        }}
      </DataSource>
    </>
  );
}
```

### pivotTotalColumnPosition

> Controls the position and visibility of pivot total columns

If specified as `false`, the pivot total columns are not displayed.

For grand-total pivot columns, see [`pivotGrandTotalColumnPosition`](https://infinite-table.com/docs/reference/infinite-table-props.md#pivotGrandTotalColumnPosition).

Pivot total columns only make sense when pivoting by two or more pivot fields, and thus will only display if this is the case. You can however, display grand-total columns if you have a single pivot field (or even no pivot fields - so [`pivotBy`](https://infinite-table.com/docs/reference/datasource-props/index.md#pivotBy) is an empty array).

In case there are no pivot fields, but [`pivotBy`](https://infinite-table.com/docs/reference/datasource-props/index.md#pivotBy) is an empty array, by default, a total column will be displayed for each aggregation (unless you specify [`pivotTotalColumnPosition`](https://infinite-table.com/docs/reference/infinite-table-props.md#pivotTotalColumnPosition) as `false`).

**Example: Pivoting with pivotTotalColumnPosition=start**

```ts
import {
  InfiniteTable,
  DataSource,
  DataSourceGroupBy,
  DataSourcePivotBy,
  InfiniteTableColumnAggregator,
  DataSourcePropAggregationReducers,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },
};

const defaultGroupBy: DataSourceGroupBy<Developer>[] = [
  {
    field: 'country',
  },
  {
    field: 'city',
  },
];

const defaultPivotBy: DataSourcePivotBy<Developer>[] = [
  {
    field: 'stack',
  },
  {
    field: 'canDesign',
    columnGroup: ({ columnGroup }) => {
      return {
        ...columnGroup,
        header:
          columnGroup.pivotGroupKey === 'yes' ? 'Designer' : 'Non-designer',
      };
    },
  },
];

const avgReducer: InfiniteTableColumnAggregator<Developer, any> = {
  initialValue: 0,

  reducer: (acc, sum) => acc + sum,
  done: (sum, arr) => Math.round(arr.length ? sum / arr.length : 0),
};

const aggregations: DataSourcePropAggregationReducers<Developer> = {
  salary: {
    ...avgReducer,
    name: 'Salary (avg)',
    field: 'salary',
  },
  age: {
    ...avgReducer,
    name: 'Age (avg)',
    field: 'age',
  },
};

export default function ColumnValueGetterExample() {
  return (
    <>
      <DataSource<Developer>
        primaryKey="id"
        defaultGroupBy={defaultGroupBy}
        defaultPivotBy={defaultPivotBy}
        aggregationReducers={aggregations}
        data={dataSource}
      >
        {({ pivotColumns, pivotColumnGroups }) => {
          return (
            <InfiniteTable<Developer>
              debugId="pivot-total-column-position-example"
              groupRenderStrategy="single-column"
              columns={columns}
              columnDefaultWidth={200}
              pivotColumns={pivotColumns}
              pivotColumnGroups={pivotColumnGroups}
              pivotTotalColumnPosition="start"
            />
          );
        }}
      </DataSource>
    </>
  );
}
```

### resizableColumns (`boolean`)

> Controls if by default all columns are resizable or not.

This property controls the behavior for all columns that don't have [`columns.resizable`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.resizable) explicitly specified.

**Example: Resizable columns example**

For resizable columns, hover the mouse between column headers to grab & drag the resize handle.

Hold SHIFT when grabbing in order to **share space on resize**.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

export default function App() {
  const [resizableColumns, setResizableColumns] = useState(true);
  return (
    <>
      <div style={{ color: 'var(--infinite-cell-color)' }}>
        <button
          style={{
            padding: 10,
            border: '2px solid currentColor',
          }}
          onClick={() => setResizableColumns((r) => !r)}
        >
          Click to toggle
        </button>

        <p style={{ padding: 10 }}>
          Columns are currently{' '}
          {resizableColumns ? 'resizable' : 'NOT RESIZABLE'}.
        </p>
      </div>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="resizableColumns-example"
          resizableColumns={resizableColumns}
          columns={columns}
          columnDefaultWidth={100}
          columnMinWidth={30}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### rowHeight (`number|string`)

> Specifies the height for rows. If a string is passed, it should be the name of a CSS variable, eg `--row-height`

**Example: rowHeight as number**

```ts files=["rowHeight-number-example.page.tsx","data.ts"]

```

**Example: rowHeight from CSS variable name**

```ts files=["rowHeight-cssvar-example.page.tsx","data.ts"]

```

### rowHoverClassName (`string?`)

> Specifies the className to be applied to a row, when it is hovered.

This property is static and cannot be a function, as applying the hover style does not trigger a re-render.
Combined with the related [`rowClassName`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowClassName) you will be able to achieve any dynamic use-case your app may need.

In the example below, we applied `bg-orange-800!` (notice the Tailwind important `!` modifier) - because the default Infinite row styles target the `background` of the rows, and not the `background-color` as Tailwind CSS `bg-*` classes. Hence the important CSS modifier.

```tsx
import * as React from 'react';
import {
  InfiniteTable,
  DataSource,
  type InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

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 columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },
  firstName: { field: 'firstName' },
  preferredLanguage: { field: 'preferredLanguage' },
  salary: { field: 'salary', type: 'number' },
};

export default function App() {
  return (
    <DataSource<Developer> primaryKey="id" data={dataSource}>
      <InfiniteTable<Developer>
        debugId="row-hover-class-name-example"
        columns={columns}
        columnDefaultWidth={200}
        rowHoverClassName="bg-orange-800!"
      />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

### rowClassName (`string|(params:InfiniteTableStylingFnParams) => string`)

> Specifies the className to be applied to all rows or conditionally to certain rows.

The `rowClassName` prop can be either a string or a function that returns a string.

When used as a function, it's called with a param of type [`InfiniteTableStylingFnParams`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableStylingFnParams), just like the [`rowStyle`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowStyle) function.

### rowStyle (`CSSProperties|(params:InfiniteTableStylingFnParams) => CSSProperties`)

> Specifies the style object to be applied to all rows or conditionally to certain rows.

The `rowStyle` prop can be either an object (typed as `React.CSSProperties`) or a function that is called with a param of type [`InfiniteTableStylingFnParams`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableStylingFnParams)

### `rowStyle` as a function

When `rowStyle` is a function, it's called with a param of type [`InfiniteTableStylingFnParams`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableStylingFnParams)

When Infinite Table calls `rowStyle`, the `data` property can be null - this is the case for grouped rows.

The `rowInfo` object contains the following properties (see [type definition here](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo)):

- `id` - the id of the current row
- `data` - the data object
- `indexInAll` - the index in the whole dataset
- `indexInGroup` - the index of the row in the current group
- `groupBy` - the fields used to group the `DataSource`
- `isGroupRow` - whether the row is a group row
- `collapsed` - for a group row, whether the group row is collapsed

See [Using RowInfo](https://infinite-table.com/docs/learn/rows/using-row-info.md) for more details.

You can either return a valid style object, or undefined.

```tsx
const rowStyle: InfiniteTablePropRowStyle<Employee> = ({
  data,
  rowInfo,
}: {
  data: Employee | null;
  rowInfo: InfiniteTableRowInfo<Employee>;
}) => {
  const salary = data ? data.salary : 0;

  if (salary > 150_000) {
    return { background: 'tomato' };
  }
  if (rowInfo.indexInAll % 10 === 0) {
    return { background: 'lightblue', color: 'black' };
  }
};
```

**Example: rowStyle example usage**

```ts files=["rowStyle-example.page.tsx","rowStyle-example-columns.ts"]

```

### viewportReservedWidth (`number`)

> Specifies the width of the space to be kept as blank - useful when there are flex columns. This number can even be negative.

The flexbox algorithm also uses `viewportReservedWidth` to determine the width of the viewport to use for sizing columns - you can use `viewportReservedWidth=100` to always have a `100px` reserved area that won't be used for flexing columns.

Or you can use a negative value, eg `-200` so the flexbox algorithm will use another `200px` (in addition to the available viewport area) for sizing flexible columns - this will result in a horizontal scrollbar being visible.

For reacting to column resizing, you need to listen to [`onViewportReservedWidthChange`](https://infinite-table.com/docs/reference/infinite-table-props.md#onViewportReservedWidthChange)

**Example: Using viewportReservedWidth to reserve whitespace when you have flexible columns**

Resize a column to see `viewportReservedWidth` updated and then click the button to reset it to `0px`

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumnSizing,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';
import * as React from 'react';
import { useState } from 'react';

export const columns: Record<string, InfiniteTableColumn<Employee>> = {
  firstName: {
    field: 'firstName',
    header: 'First Name',
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  city: {
    field: 'city',
    header: 'City',
  },
  salary: {
    field: 'salary',
    type: 'number',
    header: 'Salary',
  },
};

const defaultColumnSizing: InfiniteTablePropColumnSizing = {
  country: { flex: 1 },
  city: { flex: 1 },
  salary: { flex: 2 },
};

export default function App() {
  const [viewportReservedWidth, setViewportReservedWidth] = useState(0);
  return (
    <>
      <div style={{ color: 'var(--infinite-cell-color' }}>
        <p>Current viewport reserved width: {viewportReservedWidth}px.</p>

        <button
          style={{
            padding: 5,
            margin: 5,
            border: '2px solid currentColor',
          }}
          onClick={() => {
            setViewportReservedWidth(0);
          }}
        >
          Click to reset viewportReservedWidth to 0
        </button>
      </div>
      <DataSource<Employee> data={dataSource} primaryKey="id">
        <InfiniteTable<Employee>
          debugId="viewportReservedWidth-example"
          columns={columns}
          columnDefaultWidth={50}
          viewportReservedWidth={viewportReservedWidth}
          onViewportReservedWidthChange={setViewportReservedWidth}
          defaultColumnSizing={defaultColumnSizing}
        />
      </DataSource>
    </>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/employees100')
    .then((r) => r.json())
    .then((data: Employee[]) => data);
};

export type Employee = {
  id: number;
  companyName: string;
  companySize: string;
  firstName: string;
  lastName: string;
  country: string;
  countryCode: string;
  city: string;
  streetName: string;
  streetNo: string;
  department: string;
  team: string;
  salary: number;
  age: number;
  email: string;
};
```

### shouldAcceptEdit (`(params) => boolean|Error|Promise<boolean|Error>`)

> Function used to validate edits for all columns.

This function is called when the user wants to finish an edit - it is used to decide whether an edit is accepted or rejected, for all columns.

<p>This overrides the column-level [column.shouldAcceptEdit](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit) prop.</p>
<p>If you define the global [`shouldAcceptEdit`](https://infinite-table.com/docs/reference/infinite-table-props.md#shouldAcceptEdit) and still want to use the column-level [column.shouldAcceptEdit](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.shouldAcceptEdit), you can call the column-level function from this global one.</p>

The function is called with an object that has the following properties:

- `value` - the value that the user wants to persist via the cell editor
- `initialValue` - the initial value of the cell (the value that was displayed before editing started). This is the value resulting after [`columns.valueFormatter`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueFormatter) and [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) have been applied)
- `rawValue` - the initial value of the cell, but before any formatting and custom rendering has been applied. This is either the field value from the current data object, or the result of the column [valueGetter](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.valueGetter) function.
- `data` - the current data object
- `rowInfo` - the row info object that underlies the row
- `column` - the current column on which editing is invoked
- `api` - a reference to the [InfiniteTable API](https://infinite-table.com/docs/reference/api/index.md)
- `dataSourceApi` - - a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)

**Example**

Edit the `salary` column. Only valid numbers are persisted.

```ts
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropColumns,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  currency: string;
  stack: string;
  hobby: string;
  salary: string;
};
const dataSource: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    currency: 'USD',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'USD 1000',
  },
  {
    id: 2,
    firstName: 'Jane',
    currency: 'EUR',
    stack: 'backend',
    hobby: 'reading',
    salary: 'EUR 2000',
  },
  {
    id: 3,
    firstName: 'Jack',
    currency: 'GBP',
    stack: 'frontend',
    hobby: 'gaming',
    salary: 'GBP 3000',
  },
  {
    id: 4,
    firstName: 'Jill',
    currency: 'USD',
    stack: 'backend',
    hobby: 'reading',
    salary: 'USD 4000',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id', defaultWidth: 80 },

  salary: {
    // the only editable column
    defaultEditable: true,

    defaultWidth: 320,
    field: 'salary',
    header: 'Salary - edit accepts numbers only',
    style: { color: 'tomato' },
    getValueToEdit: ({ value }) => {
      return parseInt(value.substr(4), 10);
    },
    getValueToPersist: ({ value, data }) => {
      return `${data!.currency} ${parseInt(value, 10)}`;
    },
  },

  firstName: {
    field: 'firstName',
    header: 'Name',
  },
  currency: {
    field: 'currency',
    header: 'Currency',
  },
};

export default function InlineEditingExample() {
  const shouldAcceptEdit = ({ value }: { value: any }) => {
    return parseInt(value, 10) == value;
  };

  return (
    <>
      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="global-should-accept-edit-example"
          columns={columns}
          columnDefaultEditable={false}
          shouldAcceptEdit={shouldAcceptEdit}
        />
      </DataSource>
    </>
  );
}
```

### scrollTopKey (`number|string`)

> Determines scrolling the table to the top.

Use this property to declaratively tell the `InfiniteTable` component to scroll to the top. Whenever a new value is provided for this property, it will scroll to the top.

**Example: Declaratively scrolling to the top of the table**

```ts
import { InfiniteTable, DataSource } from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

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 dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers1k')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
const columns: InfiniteTablePropColumns<Developer> = {
  id: { field: 'id' },
  firstName: { field: 'firstName' },
  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },
  country: { field: 'country' },
  age: { field: 'age', type: 'number' },
  salary: { field: 'salary', type: 'number' },
  currency: { field: 'currency', type: 'number' },
};

export default function GroupByExample() {
  const [scrollTopKey, setScrollTopKey] = React.useState(0);
  return (
    <>
      <button
        style={{
          margin: 10,
          padding: 10,
          borderRadius: 5,
          border: '2px solid magenta',
        }}
        onClick={() => {
          setScrollTopKey((key) => key + 1);
        }}
      >
        Scroll to top
      </button>

      <DataSource<Developer> primaryKey="id" data={dataSource}>
        <InfiniteTable<Developer>
          debugId="scrollTopKey-example"
          scrollTopKey={scrollTopKey}
          columns={columns}
          columnDefaultWidth={200}
        />
      </DataSource>
    </>
  );
}
```

### virtualizeColumns (`boolean`)

> Configures whether columns are virtualized or not

By default, columns are virtualized in order to improve performance.
