# Infinite Table Hooks

> Hooks Reference page for Infinite Table - with complete examples

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

Infinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.

See below for the full list of hooks exposed by `InfiniteTable`, each with examples and code snippets.

### useMasterRowInfo

> Gives you access to the master row info in the current [RowDetail](https://infinite-table.com/docs/reference/infinite-table-props.md#components.RowDetail) component.

**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);
};
```

### useDataSourceState (`(selector: (DataSourceState) => any)`)

> You can use it in your app components that are nested inside the `<DataSource />`

```ts
import { useDataSourceState } from '@infinite-table/infinite-react'
```

Using it gives you access to the underlying data that InfiniteTable is using.

Call this hook with a `selector` function, which accepts the current `DataSourceState` as the first parameter.

```ts title="Example usage - selecting the length of the data array"

const length = useDataSourceState(state => state.dataArray.length)

```

Please make sure you know what you're doing. This is intended only for advanced and complex use-cases.

```tsx title="InfiniteTable can be nested anywhere inside the <DataSource /> component"
<DataSource>
  <h1>Your DataGrid>
  <App>
    <InfiniteTable />
  </App>
</DataSource>
```

Any component nested inside the `<DataSource />` can access the underlying data.

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

import {
  InfiniteTable,
  DataSource,
  useDataSourceState,
  type InfiniteTableColumn,
  DataSourceState,
} from '@infinite-table/infinite-react';

type Developer = {
  id: number;
  firstName: string;
  preferredLanguage: string;
  stack: string;
  salary: number;
  currency: string;
  country: string;
};

const columns: Record<string, InfiniteTableColumn<Developer>> = {
  firstName: { field: 'firstName', header: 'First Name' },
  preferredLanguage: {
    field: 'preferredLanguage',
    header: 'Programming Language',
  },
  stack: { field: 'stack', header: 'Stack' },

  salary: {
    field: 'salary',
    type: 'number',
    defaultWidth: 210,
  },
  currency: { field: 'currency', header: 'Currency', defaultWidth: 100 },
};

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

export default function App() {
  return (
    <DataSource<Developer>
      data={dataSource}
      primaryKey="id"
      defaultGroupBy={[{ field: 'country' }]}
    >
      <AppGrid />
    </DataSource>
  );
}

function AppGrid() {
  const dataLength = useDataSourceState(
    (state: DataSourceState<Developer>) => state.dataArray.length,
  );
  return (
    <div
      style={{
        height: '80vh',
        display: 'flex',
        flexDirection: 'column',
        gap: 10,
        color: 'var(--infinite-cell-color)',
        background: 'var(--infinite-background)',
      }}
    >
      <h1>Your DataGrid</h1>
      <p>
        Displaying {dataLength} rows. Collapse/expand rows to see this number
        change.
      </p>

      <InfiniteTable<Developer>
        debugId="using-datasource-context"
        groupRenderStrategy="single-column"
        defaultActiveRowIndex={0}
        domProps={domProps}
        columns={columns}
        columnDefaultWidth={150}
      />
    </div>
  );
}

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

### useInfiniteColumnCell

> Use it inside the [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) or [`column.components.ColumnCell`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.components.ColumnCell) (or [other](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) rendering functions) to retrieve information about the cell that is being rendered.

```ts
import { useInfiniteColumnCell } from '@infinite-table/infinite-react';
```

For custom column header components, see related [`useInfiniteHeaderCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteHeaderCell).

When using this hook inside a [custom column cell component](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.ColumnCell), make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.

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

  return (
    <div ref={domRef} {...props} style={{ ...props.style, color: 'red' }}>
      {props.children}
    </div>
  );
};
```

You should not pass the `domRef` along when using the hook inside the [`columns.render`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.render) or [`columns.renderValue`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.renderValue) function.

**Example: Column with render & useInfiniteColumnCell**

```tsx
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>
    </>
  );
}
```

### useInfiniteColumnEditor

> Allows you to write a custom editor to be used for [editing](https://infinite-table.com/docs/learn/editing/overview.md). The hook returns an [`InfiniteColumnEditorContextType`](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteColumnEditorContextType) object shape.

Inside this hook, you can also call [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell) to get access to the cell-related information.

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

When writing a custom editor, it's probably good to stop the propagation of the `KeyDown` event, so that the table doesn't react to the key presses (and do navigation and other stuff).

**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>
    </>
  );
}
```

### useInfiniteColumnFilterEditor (`() => ({ column, value, setValue, className, filtered,... })`)

> Used to write custom filter editors for columns.

The return value of this hook is an object with the following properties:

- `value` - the value that should be passed to the filter editor
- `setValue(value)` - the functon you have to call to update the filtering for the current column
- `column` - the current column
- `operatorName`: `string` - the name of the operator currently being applied
- `className` - a CSS class name to apply to the filter editor, for default styling
- `filtered` - a boolean indicating whether the column is currently filtered or not
- `disabled` - a boolean indicating whether the filter editor should be rendered as disabled or not
- `filterTypeKey`: `string` - the key of the filter type
- `filterType` - the filter type object for the current column
- `filterTypes` - a reference to the [`filterTypes`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterTypes) object as configured in the `DataSource`

**Example: Demo of a custom filter editor**

The `canDesign` column is using a custom `bool` filter type with a custom filter editor.

The checkbox has indeterminate state, which will match all values in the data source.

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

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

const { CheckBox } = components;

type Developer = {
  id: number;
  firstName: string;
  canDesign: boolean;
  stack: string;
  hobby: string;
};
const dataSource: Developer[] = [
  {
    id: 1,
    firstName: 'John',

    canDesign: true,
    stack: 'frontend',
    hobby: 'gaming',
  },
  {
    id: 2,
    firstName: 'Jane',

    canDesign: false,
    stack: 'backend',
    hobby: 'reading',
  },
  {
    id: 3,
    firstName: 'Jack',

    canDesign: true,
    stack: 'frontend',
    hobby: 'gaming',
  },
  {
    id: 4,
    firstName: 'Jill',

    canDesign: false,
    stack: 'backend',
    hobby: 'reading',
  },
  {
    id: 5,
    firstName: 'Seb',

    canDesign: false,
    stack: 'backend',
    hobby: 'reading',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 100,
  },
  canDesign: {
    field: 'canDesign',
    filterType: 'bool',
    renderValue: ({ value }) => (value ? 'Yes' : 'No'),
  },
  firstName: {
    field: 'firstName',
  },
  stack: { field: 'stack' },
};

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

function BoolFilterEditor() {
  const { value, setValue, className } =
    useInfiniteColumnFilterEditor<Developer>();

  return (
    <div className={className} style={{ textAlign: 'center' }}>
      <CheckBox
        checked={value}
        onChange={(newValue) => {
          if (value === true) {
            // after the value was true, make it go to indeterminate state
            newValue = null;
          }
          if (value === null) {
            // from indeterminate, goto false
            newValue = false;
          }
          setValue(newValue);
        }}
      />
    </div>
  );
}

export default () => {
  return (
    <>
      <React.StrictMode>
        <DataSource<Developer>
          data={dataSource}
          primaryKey="id"
          defaultFilterValue={[]}
          filterDelay={0}
          filterTypes={{
            bool: {
              defaultOperator: 'eq',
              emptyValues: [null],
              components: {
                FilterEditor: BoolFilterEditor,
                FilterOperatorSwitch: () => null,
              },
              operators: [
                {
                  name: 'eq',
                  label: 'Equals',
                  fn: ({ currentValue, filterValue }) =>
                    currentValue === filterValue,
                },
              ],
            },
          }}
        >
          <InfiniteTable<Developer>
            debugId="custom-filter-editor-hooks-example"
            domProps={domProps}
            columnDefaultWidth={150}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

### useInfiniteHeaderCell

> Used inside [`columns.header`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) or [`column.components.HeaderCell`](https://infinite-table.com/docs/reference/infinite-table-props.md#column.components.HeaderCell)

```ts
import { useInfiniteHeaderCell } from '@infinite-table/infinite-react';
```

For custom column cell components, see related [`useInfiniteColumnCell`](https://infinite-table.com/docs/reference/hooks/index.md#useInfiniteColumnCell).

When using this hook inside a [custom column header component](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.components.HeaderCell), make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.

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

  return (
    <div ref={domRef} {...props} style={{ ...props.style, color: 'red' }}>
      {props.children}
    </div>
  );
};
```

You should not pass the `domRef` along when using the hook inside the
[`columns.header`](https://infinite-table.com/docs/reference/infinite-table-props.md#columns.header) function.

**Example: Column with custom header & useInfiniteHeaderCell**

```tsx
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>
    </>
  );
}
```
