# DataSource API

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

When rendering the `DataSource` component, you can get access to the API by getting it from the [`onReady`](https://infinite-table.com/docs/reference/datasource-props/index.md#onReady) callback prop.

```tsx {3}
<DataSource<DATA_TYPE>
  onReady={(api: DataSourceApi<DATA_TYPE>) => {
    // api is accessible here
    // you may want to store a reference to it in a ref or somewhere in your app state
  }}
/>
```

You can also get it from the `InfiniteTable` [`onReady`](https://infinite-table.com/docs/reference/infinite-table-props.md#onReady) callback prop:

```tsx {4}
<InfiniteTable<DATA_TYPE>
  columns={[...]}
  onReady={(
    {api, dataSourceApi}: {
      api: InfiniteTableApi<DATA_TYPE>,
      dataSourceApi: DataSourceApi<DATAT_TYPE>
    }) => {

    // both api and dataSourceApi are accessible here
  }}
/>
```

For API on row/group selection, see the [Selection API page](https://infinite-table.com/docs/reference/selection-api).

### isRowDisabledAt (`(rowIndex: number) => boolean`)

> Returns `true` if the row at the specified index is disabled, `false` otherwise.

See the [`rowDisabledState`](https://infinite-table.com/docs/reference/datasource-props/index.md#rowDisabledState) prop for more information.

For checking if a row is disabled by its primary key, see the [`isRowDisabled`](https://infinite-table.com/docs/reference/datasource-api/index.md#isRowDisabled) method.

For changing the enable/disable state for the row, see the [`setRowEnabledAt`](https://infinite-table.com/docs/reference/datasource-api/index.md#setRowEnabledAt).

**Example: Changing the enable/disable state for a row**

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

import {
  DataSource,
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  RowDisabledStateObject,
} 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',
  },

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

export default () => {
  const [rowDisabledState, setRowDisabledState] = React.useState<
    RowDisabledStateObject<number>
  >({
    enabledRows: true,
    disabledRows: [1, 3, 4, 5],
  });
  return (
    <>
      <DataSource<Developer>
        data={data}
        primaryKey="id"
        rowDisabledState={rowDisabledState}
        onRowDisabledStateChange={(rowState) => {
          setRowDisabledState(rowState.getState());
        }}
      >
        <InfiniteTable<Developer>
          debugId="rowDisabledState-example"
          getCellContextMenuItems={({ rowInfo }, { dataSourceApi }) => {
            const rowDisabled = dataSourceApi.isRowDisabledAt(
              rowInfo.indexInAll,
            );
            return {
              columns: [{ name: 'label' }],
              items: [
                {
                  label: 'Disable row',
                  disabled: rowDisabled,
                  key: 'disable-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabledAt(rowInfo.indexInAll, false);
                    hideMenu();
                  },
                },
                {
                  label: 'Enable row',
                  disabled: !rowDisabled,
                  key: 'enable-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabled(rowInfo.id, true);
                    hideMenu();
                  },
                },
                {
                  label: 'Toggle row enable/disable',
                  key: 'toggle-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabled(
                      rowInfo.id,
                      dataSourceApi.isRowDisabled(rowInfo.id),
                    );
                    hideMenu();
                  },
                },
              ],
            };
          }}
          keyboardNavigation="row"
          columnDefaultWidth={120}
          columnMinWidth={50}
          columns={columns}
        />
      </DataSource>
    </>
  );
};
```

### isRowDisabled (`(primaryKey: any) => boolean`)

> Returns `true` if the row with the specified primary key is disabled, `false` otherwise.

See the [`rowDisabledState`](https://infinite-table.com/docs/reference/datasource-props/index.md#rowDisabledState) prop for more information.

For checking if a row is disabled by its index, see the [`isRowDisabledAt`](https://infinite-table.com/docs/reference/datasource-api/index.md#isRowDisabledAt) method.

For changing the enable/disable state for the row, see the [`setRowEnabled`](https://infinite-table.com/docs/reference/datasource-api/index.md#setRowEnabled).

**Example: Changing the enable/disable state for a row**

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

import {
  DataSource,
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  RowDisabledStateObject,
} 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',
  },

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

export default () => {
  const [rowDisabledState, setRowDisabledState] = React.useState<
    RowDisabledStateObject<number>
  >({
    enabledRows: true,
    disabledRows: [1, 3, 4, 5],
  });
  return (
    <>
      <DataSource<Developer>
        data={data}
        primaryKey="id"
        rowDisabledState={rowDisabledState}
        onRowDisabledStateChange={(rowState) => {
          setRowDisabledState(rowState.getState());
        }}
      >
        <InfiniteTable<Developer>
          debugId="rowDisabledState-example"
          getCellContextMenuItems={({ rowInfo }, { dataSourceApi }) => {
            const rowDisabled = dataSourceApi.isRowDisabledAt(
              rowInfo.indexInAll,
            );
            return {
              columns: [{ name: 'label' }],
              items: [
                {
                  label: 'Disable row',
                  disabled: rowDisabled,
                  key: 'disable-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabledAt(rowInfo.indexInAll, false);
                    hideMenu();
                  },
                },
                {
                  label: 'Enable row',
                  disabled: !rowDisabled,
                  key: 'enable-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabled(rowInfo.id, true);
                    hideMenu();
                  },
                },
                {
                  label: 'Toggle row enable/disable',
                  key: 'toggle-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabled(
                      rowInfo.id,
                      dataSourceApi.isRowDisabled(rowInfo.id),
                    );
                    hideMenu();
                  },
                },
              ],
            };
          }}
          keyboardNavigation="row"
          columnDefaultWidth={120}
          columnMinWidth={50}
          columns={columns}
        />
      </DataSource>
    </>
  );
};
```

### setRowEnabled (`(primaryKey: any, enabled: boolean) => void`)

> Sets the enable/disable state for the row with the specified primary key.

See the [`rowDisabledState`](https://infinite-table.com/docs/reference/datasource-props/index.md#rowDisabledState) prop for more information.

For setting the enable/disable state for a row by its index, see the [`setRowEnabledAt`](https://infinite-table.com/docs/reference/datasource-api/index.md#setRowEnabledAt) method.

**Example: Changing the enable/disable state for a row**

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

import {
  DataSource,
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  RowDisabledStateObject,
} 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',
  },

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

export default () => {
  const [rowDisabledState, setRowDisabledState] = React.useState<
    RowDisabledStateObject<number>
  >({
    enabledRows: true,
    disabledRows: [1, 3, 4, 5],
  });
  return (
    <>
      <DataSource<Developer>
        data={data}
        primaryKey="id"
        rowDisabledState={rowDisabledState}
        onRowDisabledStateChange={(rowState) => {
          setRowDisabledState(rowState.getState());
        }}
      >
        <InfiniteTable<Developer>
          debugId="rowDisabledState-example"
          getCellContextMenuItems={({ rowInfo }, { dataSourceApi }) => {
            const rowDisabled = dataSourceApi.isRowDisabledAt(
              rowInfo.indexInAll,
            );
            return {
              columns: [{ name: 'label' }],
              items: [
                {
                  label: 'Disable row',
                  disabled: rowDisabled,
                  key: 'disable-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabledAt(rowInfo.indexInAll, false);
                    hideMenu();
                  },
                },
                {
                  label: 'Enable row',
                  disabled: !rowDisabled,
                  key: 'enable-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabled(rowInfo.id, true);
                    hideMenu();
                  },
                },
                {
                  label: 'Toggle row enable/disable',
                  key: 'toggle-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabled(
                      rowInfo.id,
                      dataSourceApi.isRowDisabled(rowInfo.id),
                    );
                    hideMenu();
                  },
                },
              ],
            };
          }}
          keyboardNavigation="row"
          columnDefaultWidth={120}
          columnMinWidth={50}
          columns={columns}
        />
      </DataSource>
    </>
  );
};
```

### treeApi (`TreeApi<DATA_TYPE>`)

> A reference to the [Tree API](https://infinite-table.com/docs/reference/tree-api/index.md).

When using the `<TreeDataSource />` component, this property will be available on the `DataSourceApi` instance.

### setRowEnabledAt (`(rowIndex: number, enabled: boolean) => void`)

> Sets the enable/disable state for the row at the specified index.

See the [`rowDisabledState`](https://infinite-table.com/docs/reference/datasource-props/index.md#rowDisabledState) prop for more information.

For setting the enable/disable state for a row by its primary key, see the [`setRowEnabled`](https://infinite-table.com/docs/reference/datasource-api/index.md#setRowEnabled) method.

**Example: Changing the enable/disable state for a row**

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

import {
  DataSource,
  DataSourceData,
  InfiniteTable,
  InfiniteTablePropColumns,
  RowDisabledStateObject,
} 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',
  },

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

export default () => {
  const [rowDisabledState, setRowDisabledState] = React.useState<
    RowDisabledStateObject<number>
  >({
    enabledRows: true,
    disabledRows: [1, 3, 4, 5],
  });
  return (
    <>
      <DataSource<Developer>
        data={data}
        primaryKey="id"
        rowDisabledState={rowDisabledState}
        onRowDisabledStateChange={(rowState) => {
          setRowDisabledState(rowState.getState());
        }}
      >
        <InfiniteTable<Developer>
          debugId="rowDisabledState-example"
          getCellContextMenuItems={({ rowInfo }, { dataSourceApi }) => {
            const rowDisabled = dataSourceApi.isRowDisabledAt(
              rowInfo.indexInAll,
            );
            return {
              columns: [{ name: 'label' }],
              items: [
                {
                  label: 'Disable row',
                  disabled: rowDisabled,
                  key: 'disable-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabledAt(rowInfo.indexInAll, false);
                    hideMenu();
                  },
                },
                {
                  label: 'Enable row',
                  disabled: !rowDisabled,
                  key: 'enable-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabled(rowInfo.id, true);
                    hideMenu();
                  },
                },
                {
                  label: 'Toggle row enable/disable',
                  key: 'toggle-row',
                  onAction: ({ hideMenu }) => {
                    dataSourceApi.setRowEnabled(
                      rowInfo.id,
                      dataSourceApi.isRowDisabled(rowInfo.id),
                    );
                    hideMenu();
                  },
                },
              ],
            };
          }}
          keyboardNavigation="row"
          columnDefaultWidth={120}
          columnMinWidth={50}
          columns={columns}
        />
      </DataSource>
    </>
  );
};
```

### replaceAllData (`(data: DATA_TYPE[], options?: DataSourceCRUDParam) => Promise`)

> Replaces all data in the DataSource with the provided data.

Clears the current data array in the `<DataSource />` and replaces it with the provided data.

When calling this, if there are pending data mutations, they will be discarded.

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

### clearAllData (`() => Promise`)

> Clears all data in the DataSource.

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

### addData (`(data: DATA_TYPE) => Promise`)

> Adds the specified data at the end of the data source.

The given data param should be of type `DATA_TYPE` (the TypeScript generic data type that the `DataSource` was bound to).

For adding an array of data, see the [`addDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#addDataArray) method.

If the component has [sorting](https://infinite-table.com/docs/reference/datasource-props/index.md#sortInfo), the added data might not be displayed at the end of the data source.

This method batches data updates and waits for a request animation frame until it persists the data to the `DataSource`. This means you can execute multiple calls to `addData` (or [`updateData`](https://infinite-table.com/docs/reference/datasource-props/index.md#updateData), [`removeData`](https://infinite-table.com/docs/reference/datasource-props/index.md#removeData), [`insertData`](https://infinite-table.com/docs/reference/datasource-props/index.md#insertData)) in the same frame and they will be batched and persisted together.

The return value is a `Promise` that resolves when the data has been added. When multiple `addData` (and friends) calls are executed in the same frame, the result of those calls is a reference to the same promise.

```ts
const promise1 = dataSourceApi.add({ ... })
const promise2 = dataSourceApi.add({ ... })
const promise3 = dataSourceApi.insertData({ ... }, { position: 'before', primaryKey: 4 })

// promise1, promise2 and promise3 are the same promise
// as the calls are run in the same raf and batched together
// promise1 === promise2
// promise1 === promise3
```

For adding an array of data, see the [`addDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#addDataArray) method.
For inserting data at a specific position, see the [`insertData`](https://infinite-table.com/docs/reference/datasource-api/index.md#insertData) method.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

**Example: Using DataSourceApi.addData to update the DataSource**

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

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

type Developer = {
  id: number;

  firstName: string;
  lastName: string;

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

  age: number;
};

const data: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    lastName: 'Bob',
    age: 20,
    canDesign: 'yes',
    currency: 'USD',
    preferredLanguage: 'JavaScript',
    stack: 'frontend',
  },
  {
    id: 2,
    firstName: 'Marry',
    lastName: 'Bob',
    age: 25,
    canDesign: 'yes',
    currency: 'USD',
    preferredLanguage: 'JavaScript',
    stack: 'frontend',
  },
  {
    id: 3,
    firstName: 'Bill',
    lastName: 'Bobson',
    age: 30,
    canDesign: 'no',
    currency: 'CAD',
    preferredLanguage: 'TypeScript',
    stack: 'frontend',
  },
  {
    id: 4,
    firstName: 'Mark',
    lastName: 'Twain',
    age: 31,
    canDesign: 'yes',
    currency: 'CAD',
    preferredLanguage: 'Rust',
    stack: 'backend',
  },
  {
    id: 5,
    firstName: 'Matthew',
    lastName: 'Hilson',
    age: 29,
    canDesign: 'yes',
    currency: 'CAD',
    preferredLanguage: 'Go',
    stack: 'backend',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
  },
  firstName: {
    field: 'firstName',
  },
  age: {
    field: 'age',
    type: 'number',
  },

  stack: { field: 'stack', renderMenuIcon: false },
  currency: { field: 'currency' },
};

let ID = data.length;
const currencies = ['USD', 'CAD', 'EUR', 'GBP', 'RUB', 'UAH', 'CNY'];
const getMark = (): Developer => ({
  id: ++ID,
  firstName: 'Mark',
  lastName: 'Berg',
  // random int from 20 to 60
  age: Math.floor(Math.random() * (60 - 20 + 1)) + 20,
  canDesign: 'no',
  //random currency
  currency: currencies[Math.floor(Math.random() * currencies.length)],
  preferredLanguage: 'Go',
  stack: 'frontend',
});

export default () => {
  const [dataSourceApi, setDataSourceApi] =
    React.useState<DataSourceApi<Developer>>();
  return (
    <>
      <button
        onClick={() => {
          dataSourceApi!.addData(getMark());
        }}
      >
        Add Mark at the end
      </button>

      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          onReady={setDataSourceApi}
        >
          <InfiniteTable<Developer>
            debugId="addData-example"
            columnDefaultWidth={100}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

### addDataArray (`(data: DATA_TYPE[]) => Promise`)

> Adds an array of data at the end of the data source

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

For adding at the beginning of the data source, see the [`insertDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#insertDataArray) method.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

### getDataByNodePath (`(nodePath: NodePath) => DATA_TYPE | null`)

> Retrieves the data object for the specified node path.

**Example: Retrieving data by node path**

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

import { useState } from 'react';

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

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', header: 'Name', renderTreeIcon: true },
  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 [dataSourceApi, setDataSourceApi] =
    useState<DataSourceApi<FileSystemNode> | null>();
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: '10px',
        }}
      >
        <div style={{ display: 'flex', gap: 10, padding: 10 }}>
          <button
            onClick={() => {
              const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
              if (!rowInfo || !rowInfo.isTreeNode) {
                return;
              }
              const nodePath = rowInfo.nodePath;

              const data = dataSourceApi!.getDataByNodePath(nodePath);
              alert(data?.name);
            }}
          >
            Alert <code>name</code> property for current node.
          </button>
        </div>
      </div>
      <TreeDataSource
        nodesKey="children"
        primaryKey="id"
        data={dataSource}
        onReady={setDataSourceApi}
      >
        <TreeGrid
          columns={columns}
          activeRowIndex={activeIndex}
          onActiveRowIndexChange={setActiveIndex}
        />
      </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: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
        },
      ],
    },
    {
      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',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};
```

### getDataByPrimaryKey (`(primaryKey: string | number) => DATA_TYPE | null`)

> Retrieves the data object for the specified primary key.

You can call this method to retrieve objects from the data source even when they have been filtered out via [`filterValue`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterValue) or [`filterFunction`](https://infinite-table.com/docs/reference/datasource-props/index.md#filterFunction), as long as they are present in the initial data.

The alternative API method [`getRowInfoByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-api/index.md#getRowInfoByPrimaryKey) can only be used to retrieve [row info objects](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) of rows that are not filtered out - so only rows that match the filtering, if one is present.

### getIndexByPrimaryKey (`(id: any) => number`)
> Retrieves the index of a row by its primary key. If the row is not found, returns `-1`. See related [`getPrimaryKeyByIndex`](https://infinite-table.com/docs/reference/datasource-api/index.md#getPrimaryKeyByIndex)

The primary key you pass in needs to exist in the current data set. If you pass in a primary key that has been filtered out or that's not in the data set, the method will return `-1`.

### getPrimaryKeyByIndex (`(index: number) => any | undefined `)

> Retrieves the primary key of a row by its current index. If the row is not found, returns `undefined`. See related [`getIndexByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-api/index.md#getIndexByPrimaryKey)

The index needs to be of an existing row, after all filtering is applied. If you pass in an non-existent index, the method will return `undefined`.

### getDataByNodePath (`(nodePath: any[]) => DATA_TYPE | null`)

> Retrieves the data object for the node with the specified path. If the node is not found, returns `null`. See related [`getDataByIndex`](https://infinite-table.com/docs/reference/datasource-api/index.md#getDataByIndex). See related [`getRowInfoByNodePath`](https://infinite-table.com/docs/reference/datasource-api/index.md#getRowInfoByNodePath).

The node path needs to be of an existing node. If you pass in a non-existent (or filtered out) node path, the method will return `null`.

**Example: Retrieving data by node path**

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

import { useState } from 'react';

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

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', header: 'Name', renderTreeIcon: true },
  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 [dataSourceApi, setDataSourceApi] =
    useState<DataSourceApi<FileSystemNode> | null>();
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: '10px',
        }}
      >
        <div style={{ display: 'flex', gap: 10, padding: 10 }}>
          <button
            onClick={() => {
              const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
              if (!rowInfo || !rowInfo.isTreeNode) {
                return;
              }
              const nodePath = rowInfo.nodePath;

              const data = dataSourceApi!.getDataByNodePath(nodePath);
              alert(data?.name);
            }}
          >
            Alert <code>name</code> property for current node.
          </button>
        </div>
      </div>
      <TreeDataSource
        nodesKey="children"
        primaryKey="id"
        data={dataSource}
        onReady={setDataSourceApi}
      >
        <TreeGrid
          columns={columns}
          activeRowIndex={activeIndex}
          onActiveRowIndexChange={setActiveIndex}
        />
      </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: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
        },
      ],
    },
    {
      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',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};
```

### getRowInfoByNodePath (`(nodePath: any[]) => InfiniteTableRowInfo<DATA_TYPE> | null`)

> Retrieves the [row info object](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) for the node with the specified path. If the node is not found, returns `null`. See related [`getDataByNodePath`](https://infinite-table.com/docs/reference/datasource-api/index.md#getDataByNodePath).

The node path needs to be of an existing node. If you pass in a non-existent (or filtered out) node path, the method will return `null`.

### getRowInfoByIndex (`(index: number) => InfiniteTableRowInfo<DATA_TYPE> | null`)

> Retrieves the [row info object](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) for the row at the specified index. If none found, returns `null`. See related [`getRowInfoByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-api/index.md#getRowInfoByPrimaryKey).

### getRowInfoByPrimaryKey (`(id: any) => InfiniteTableRowInfo<DATA_TYPE> | null`)

> Retrieves the [row info object](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo) for the row with the specified primary key. If none found, returns `null`.

This method will only find row info objects for rows that are currently in the dataset and matching the filtering, if one is present. Can also be called for group rows.

See related [`getDataByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-api/index.md#getDataByPrimaryKey) method, which retrieves the raw data object for the specified primary key, even if it has been filtered out.

### getRowInfoArray (`() => InfiniteTableRowInfo[]`)

> Returns the current row info array. See [the type definition of the row info object](https://infinite-table.com/docs/reference/type-definitions/index.md#InfiniteTableRowInfo).

The row info array represents the current state of the DataSource. This array may contain more items than the actual data array fetched initially by the DataSource. This is because it includes group rows, when grouping is defined, as well as unfetched rows in some advanced scenarios.

### insertData (`(data: DATA_TYPE, { position, primaryKey }) => Promise`)

> Inserts the given data at the specified position relative to the given primary key.

The `position` can be one of the following:

- `start` | `end` - inserts the data at the beginning or end of the data source. In this case, no `primaryKey` is needed.
- `before` | `after` - inserts the data before or after the data item that has the specified primary key. **In thise case, the `primaryKey` is required.**

We're intentionally not encouraging inserting at a specified `index`, as the index of rows in the visible viewport can change as the user sorts, filters or groups the data.

For inserting an array of data, see the [`insertDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#insertDataArray) method.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

**Example: Inserting data at various locations**

Click any row in the table to make it the current active row, and then use the second button to add a new row after the active row.

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

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

type Developer = {
  id: number;

  firstName: string;
  lastName: string;

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

  age: number;
  reposCount: number;
};

export function getRandomInt(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

const CURRENCIES = ['USD', 'CAD', 'EUR'];
const stacks = ['frontend', 'backend', 'fullstack'];

let ID = 0;

const firstNames = ['John', 'Jane', 'Bob', 'Alice', 'Mike', 'Molly'];
const lastNames = ['Smith', 'Doe', 'Johnson', 'Williams', 'Brown', 'Jones'];
const getRow = (count?: number): Developer => {
  return {
    id: ID++,
    firstName:
      ID === 1
        ? 'ROCKY'
        : firstNames[getRandomInt(0, firstNames.length - 1)] +
          (count ? ` ${count}` : ''),
    lastName: lastNames[getRandomInt(0, firstNames.length - 1)],

    currency: CURRENCIES[getRandomInt(0, 2)],
    salary: getRandomInt(1000, 10000),
    preferredLanguage: 'JavaScript',
    stack: stacks[getRandomInt(0, 2)],
    canDesign: getRandomInt(0, 1) === 0 ? 'yes' : 'no',
    age: getRandomInt(20, 100),
    reposCount: getRandomInt(0, 100),
  };
};

const dataSource: Developer[] = [...Array(10)].map(getRow);

const columns: InfiniteTablePropColumns<Developer> = {
  firstName: {
    field: 'firstName',
  },
  age: {
    field: 'age',
    type: 'number',
    style: ({ value, rowInfo }) => {
      if (rowInfo.isGroupRow) {
        return {};
      }

      return {
        color: 'black',
        background:
          value > 80
            ? 'tomato'
            : value > 60
            ? 'orange'
            : value > 40
            ? 'yellow'
            : value > 20
            ? 'lightgreen'
            : 'green',
      };
    },
  },
  salary: {
    field: 'salary',
    type: 'number',
  },
  reposCount: {
    field: 'reposCount',
    type: 'number',
  },

  stack: { field: 'stack', renderMenuIcon: false },
  currency: { field: 'currency' },
};

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

const buttonStyle = {
  border: '2px solid magenta',
  color: 'var(--infinite-cell-color)',
  background: 'var(--infinite-background)',
};
export default () => {
  const [apis, onReady] = React.useState<{
    api: InfiniteTableApi<Developer>;
    dataSourceApi: DataSourceApi<Developer>;
  }>();

  const [currentActivePrimaryKey, setCurrentActivePrimaryKey] =
    React.useState<string>('');

  return (
    <React.StrictMode>
      <button
        style={buttonStyle}
        onClick={() => {
          if (apis) {
            const dataSourceApi = apis.dataSourceApi!;
            dataSourceApi.insertData(
              getRow(dataSourceApi.getRowInfoArray().length),
              {
                primaryKey: 0,
                position: 'after',
              },
            );
          }
        }}
      >
        Add row after Rocky
      </button>
      <button
        style={buttonStyle}
        disabled={!currentActivePrimaryKey}
        onClick={() => {
          if (apis) {
            const dataSourceApi = apis.dataSourceApi!;
            dataSourceApi.insertData(
              getRow(dataSourceApi.getRowInfoArray().length),
              {
                primaryKey: currentActivePrimaryKey,
                position: 'after',
              },
            );
          }
        }}
      >
        Add row after currently active row
      </button>
      <DataSource<Developer> data={dataSource} primaryKey="id">
        <InfiniteTable<Developer>
          debugId="insert-example"
          domProps={domProps}
          onReady={onReady}
          columnDefaultWidth={130}
          columnMinWidth={50}
          columns={columns}
          keyboardNavigation="row"
          onActiveRowIndexChange={(rowIndex) => {
            const id = apis?.dataSourceApi.getRowInfoArray()[rowIndex].id;

            setCurrentActivePrimaryKey(id);
          }}
        />
      </DataSource>
    </React.StrictMode>
  );
};
```

### insertDataArray (`(data: DATA_TYPE[], { position, primaryKey?, nodePath?, waitForNode? }) => Promise`)

> Inserts an array of data at the specified position (and relative to the given primary key or node path).

Just like the [`insertData`](https://infinite-table.com/docs/reference/datasource-api/index.md#insertData) method, the `position` can be one of the following:

- `start` | `end` - inserts the data at the beginning or end of the data source. In this case, no `primaryKey` is needed. If `nodePath` is provided, `"start"` means insert the data at the start of the children array of that node; `"end"` means insert the data at the end of the children array of that node.
- `before` | `after` - inserts the data before or after the data item that has the specified primary key / node path. **In thise case, the `primaryKey` or the `nodePath` is required.**

All the data items passed to this method will be inserted (in the order in the array) at the specified position.

When using this method for tree nodes, `waitForNode` defaults to `true` (you can specify a boolean value, or a number to override the default timeout). When `waitForNode` is `true`, the method will insert the data in the respective node's children array, after making sure the node exists.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

### updateDataArrayByNodePath (`({data, nodePath}[]) => Promise`)

> Updates the data for the nodes specified by the node paths.

The first parameter should be an array of objects, where each object has a `data` property (the data to update) and a `nodePath` property (the path of the node to update).

```tsx title="Updating an aray of tree nodes"
dataSourceApi.updateDataArrayByNodePath([
  {
    data: {
      fileName: 'Vacation.pdf',
      sizeInKB: 1000,
    },
    nodePath: ['1', '10'],
  },
  {
    data: {
      fileName: 'Report.docx',
      sizeInKB: 2000,
    },
    nodePath: ['1', '11'],
  },
]);
```

### updateChildrenByNodePath (`(children: DATA_TYPE[] | any | (children, data) => DATA_TYPE[] | any, nodePath: NodePath) => Promise, options?`)

> Updates the children of the node specified by the node path.

The first parameter can be an array (or `null` or `undefined`) or a function that returns an array (or `null` or `undefined`).

The second parameter is the node path.

When a function is passed as the first parameter, it will be called with the children of the node. The return value will be used as the new children of the node. This gives you an opportunity to update the children based on the current children state.

```tsx title="Updating the children of a tree node"
dataSourceApi.updateChildrenByNodePath(
  (currentChildren) => {
    return [
      ...(currentChildren || []),
      {
        name: 'untitled.txt',
        id: '8',
      },
    ];
  },
  ['1', '3'],
);
```

When using a function, it will be called with the current children of the node (1st parameter) and also with the node data object (2nd parameter).

As a third parameter, you can pass in an options object, which supports the `waitForNode` property (`boolean` or `number` to override the default timeout). When `waitForNode` is `true`, the method will update the children of the node, after making sure the node exists (and waiting for the specified timeout if needed).

### waitForNodePath (`(nodePath: NodePath, options?: { timeout?: number }) => Promise<boolean>`)

> Returns a promise that tells if the node path exists.

Calling this method will give you a promise that will tell you if the node path exists or not.

If the `DataSource` can find the node path either immediately or before the specified timeout expires, the promise will resolve to `true`, otherwise it will resolve to `false`.

If no `timeout` is specified, it will default to `1000`ms.

### updateDataByNodePath (`(data: Partial<DATA_TYPE>, nodePath: NodePath, options?) => Promise`)

> Updates the data for the node specified by the node path.

If the primary keys in your `<TreeDataSource />` are unique globally (not just within the same node), you can still use the [`updateData`](https://infinite-table.com/docs/reference/datasource-props/index.md#updateData) method.

```tsx title="Updating a tree node by path"
dataSourceApi.updateDataByNodePath({
  fileName: 'New Name',
  sizeInKB: 1000,
}, ['1', '10']);
```

**Example: Updating a tree node by path**

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

import { useState } from 'react';

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

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', header: 'Name', renderTreeIcon: true },
  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 [dataSourceApi, setDataSourceApi] =
    useState<DataSourceApi<FileSystemNode> | null>();
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: '10px',
        }}
      >
        <div>Use the buttons below to update the current active node.</div>
        <div style={{ display: 'flex', gap: 10, padding: 10 }}>
          <button
            onClick={() => {
              const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
              if (!rowInfo || !rowInfo.isTreeNode) {
                return;
              }
              const nodePath = rowInfo.nodePath;
              const randomSize = Math.floor(Math.random() * 10000) + 1;

              dataSourceApi!.updateDataByNodePath(
                { sizeInKB: randomSize, name: getRandomFileName() },
                nodePath,
              );
            }}
          >
            Update current node
          </button>
          <button
            onClick={() => {
              const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
              if (!rowInfo || !rowInfo.isTreeNode) {
                return;
              }
              const nodePath = rowInfo.nodePath;
              dataSourceApi!.updateDataByNodePath(
                {
                  children: undefined,
                },
                nodePath,
              );
            }}
          >
            Remove children
          </button>

          <button
            onClick={() => {
              const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
              if (!rowInfo || !rowInfo.isTreeNode) {
                return;
              }
              const nodePath = rowInfo.nodePath;
              const count = Math.floor(Math.random() * 5) + 1;
              const generated: FileSystemNode[] = Array.from(
                { length: count },
                () => ({
                  id: `generated-${times++}`,
                  name: getRandomFileName(),
                  sizeInKB: Math.floor(Math.random() * 10000) + 1,
                  type: 'file',
                }),
              );
              dataSourceApi!.updateDataByNodePath(
                {
                  children: [...(rowInfo.data.children || []), ...generated],
                },
                nodePath,
              );
            }}
          >
            Add children
          </button>
        </div>
      </div>
      <TreeDataSource
        nodesKey="children"
        primaryKey="id"
        data={dataSource}
        onReady={setDataSourceApi}
      >
        <TreeGrid
          columns={columns}
          activeRowIndex={activeIndex}
          onActiveRowIndexChange={setActiveIndex}
        />
      </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: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
        },
      ],
    },
    {
      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',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};

let times = 0;
const getRandomFileName = () => {
  const names = [
    'Report',
    'Vacation',
    'CV',
    'Unknown',
    'Sales.jpg',
    'Finances.xls',
    'Presentation.ppt',
    'Budget.xlsx',
    'Notes.txt',
    'Resume.docx',
    'Agenda.docx',
    'Summary.docx',
    'Proposal.docx',
    'Report.docx',
    'Invoice.pdf',
    'Memo.docx',
    'Letter.docx',
    'Plan.docx',
    'Guide.docx',
  ];
  const name = names[Math.floor(Math.random() * names.length)];
  const [namePart, extension] = name.split('.');
  times++;
  return `${namePart}${times}${extension ? `.${extension}` : ''}`;
};
```

The third parameter is an options object, which can have a `waitForNode` property (either `boolean` or `number` - use a `number` to override the default timeout). NOTE: if you don't pass it, it defaults to `1000ms`.

When `waitForNode` is used, if the node does not exist yet, it will wait for the specified timeout and then update the data.

### removeDataByNodePath (`(nodePath: NodePath) => Promise`)

> Removes the node specified by the specified node path.

If the primary keys in your `<TreeDataSource />` are unique globally (not just within the same node), you can still use the [`removeDataByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-props/index.md#removeDataByPrimaryKey) method.

```tsx title="Removing a tree node by path"
dataSourceApi.removeDataByNodePath(['1', '10']);
```

**Example: Removing a tree node by path**

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

import { useState } from 'react';

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

const columns: Record<string, InfiniteTableColumn<FileSystemNode>> = {
  name: { field: 'name', header: 'Name', renderTreeIcon: true },
  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 [dataSourceApi, setDataSourceApi] =
    useState<DataSourceApi<FileSystemNode> | null>();
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <div
        style={{
          color: 'var(--infinite-cell-color)',
          padding: '10px',
        }}
      >
        <div>Use the buttons below to update the current active node.</div>
        <div style={{ display: 'flex', gap: 10, padding: 10 }}>
          <button
            onClick={() => {
              const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
              if (!rowInfo || !rowInfo.isTreeNode) {
                return;
              }
              const nodePath = rowInfo.nodePath;
              const randomSize = Math.floor(Math.random() * 10000) + 1;

              dataSourceApi!.updateDataByNodePath(
                { sizeInKB: randomSize, name: getRandomFileName() },
                nodePath,
              );
            }}
          >
            Update current node
          </button>
          <button
            onClick={() => {
              const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
              if (!rowInfo || !rowInfo.isTreeNode) {
                return;
              }
              const nodePath = rowInfo.nodePath;
              dataSourceApi!.updateDataByNodePath(
                {
                  children: undefined,
                },
                nodePath,
              );
            }}
          >
            Remove children for current node
          </button>

          <button
            onClick={() => {
              const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
              if (!rowInfo || !rowInfo.isTreeNode) {
                return;
              }
              const nodePath = rowInfo.nodePath;

              dataSourceApi!.removeDataByNodePath(nodePath);
            }}
          >
            Remove current node
          </button>
        </div>
      </div>
      <TreeDataSource
        nodesKey="children"
        primaryKey="id"
        data={dataSource}
        onReady={setDataSourceApi}
      >
        <TreeGrid
          columns={columns}
          activeRowIndex={activeIndex}
          onActiveRowIndexChange={setActiveIndex}
        />
      </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: '20',
          name: 'unknown.txt',
          sizeInKB: 100,
          type: 'file',
        },
      ],
    },
    {
      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',
              mimeType: 'video/mp4',
            },
          ],
        },
      ],
    },
  ];
  return Promise.resolve(nodes);
};

let times = 0;
const getRandomFileName = () => {
  const names = [
    'Report',
    'Vacation',
    'CV',
    'Unknown',
    'Sales.jpg',
    'Finances.xls',
    'Presentation.ppt',
    'Budget.xlsx',
    'Notes.txt',
    'Resume.docx',
    'Agenda.docx',
    'Summary.docx',
    'Proposal.docx',
    'Report.docx',
    'Invoice.pdf',
    'Memo.docx',
    'Letter.docx',
    'Plan.docx',
    'Guide.docx',
  ];
  const name = names[Math.floor(Math.random() * names.length)];
  const [namePart, extension] = name.split('.');
  times++;
  return `${namePart}${times}${extension ? `.${extension}` : ''}`;
};
```

### updateData (`(data: Partial<DATA_TYPE>, options?) => Promise`)

> Updates the data item to match the given data object. For updating tree nodes by path, see the [`updateDataByNodePath`](https://infinite-table.com/docs/reference/datasource-api/index.md#updateDataByNodePath) method.

The data object must have a primary key that matches the primary key of the data item that you want to update. Besides the primary key, it can contain any number of properties that you want to update.

```ts
dataSourceApi.updateData({
  // if the primaryKey is the id, make sure to include it
  id: 1,

  // and then include any properties you want to update - in this case, the name and age
  name: 'John Doe',
  age: 30,
});
```

**Example: Updating a row**

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

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

type Developer = {
  id: number;

  firstName: string;
  lastName: string;

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

  age: number;
};

const data: Developer[] = [
  {
    id: 1,
    firstName: 'John',
    lastName: 'Bob',
    age: 20,
    canDesign: 'yes',
    currency: 'USD',
    preferredLanguage: 'JavaScript',
    stack: 'frontend',
  },
  {
    id: 2,
    firstName: 'Marry',
    lastName: 'Bob',
    age: 25,
    canDesign: 'yes',
    currency: 'USD',
    preferredLanguage: 'JavaScript',
    stack: 'frontend',
  },
  {
    id: 3,
    firstName: 'Bill',
    lastName: 'Bobson',
    age: 30,
    canDesign: 'no',
    currency: 'CAD',
    preferredLanguage: 'TypeScript',
    stack: 'frontend',
  },
  {
    id: 4,
    firstName: 'Mark',
    lastName: 'Twain',
    age: 31,
    canDesign: 'yes',
    currency: 'CAD',
    preferredLanguage: 'Rust',
    stack: 'backend',
  },
  {
    id: 5,
    firstName: 'Matthew',
    lastName: 'Hilson',
    age: 29,
    canDesign: 'yes',
    currency: 'CAD',
    preferredLanguage: 'Go',
    stack: 'backend',
  },
];

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
  },
  firstName: {
    field: 'firstName',
  },
  age: {
    field: 'age',
    type: 'number',
  },

  stack: { field: 'stack', renderMenuIcon: false },
  currency: { field: 'currency' },
};

const currencies = ['USD', 'CAD', 'EUR', 'GBP', 'RUB', 'UAH', 'CNY'];
const stacks = ['frontend', 'backend', 'fullstack'];
const languages = [
  'JavaScript',
  'TypeScript',
  'Go',
  'Rust',
  'Python',
  'Java',
  'C#',
];
const getRandomDeveloperUpdate = (data: Developer): Partial<Developer> => ({
  id: data.id,
  age: Math.floor(Math.random() * (60 - 20 + 1)) + 20,
  canDesign: ['yes', 'no'][
    Math.floor(Math.random() * 2)
  ] as Developer['canDesign'],

  currency: currencies[Math.floor(Math.random() * currencies.length)],
  preferredLanguage: languages[Math.floor(Math.random() * languages.length)],
  stack: stacks[Math.floor(Math.random() * stacks.length)],
});

export default () => {
  const [dataSourceApi, setDataSourceApi] =
    React.useState<DataSourceApi<Developer>>();
  const [activeIndex, setActiveIndex] = React.useState(0);
  return (
    <>
      <button
        onClick={() => {
          const rowInfo = dataSourceApi!.getRowInfoByIndex(activeIndex);
          if (rowInfo && rowInfo.data) {
            dataSourceApi!.updateData(
              getRandomDeveloperUpdate(rowInfo.data as Developer),
            );
          }
        }}
      >
        Update current row
      </button>

      <React.StrictMode>
        <DataSource<Developer>
          data={data}
          primaryKey="id"
          onReady={setDataSourceApi}
        >
          <InfiniteTable<Developer>
            debugId="simple-updateData-example"
            activeRowIndex={activeIndex}
            onActiveRowIndexChange={setActiveIndex}
            keyboardNavigation="row"
            columnDefaultWidth={100}
            columnMinWidth={50}
            columns={columns}
          />
        </DataSource>
      </React.StrictMode>
    </>
  );
};
```

For updating an array of data, see the [`updateDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#updateDataArray) method.

The second parameter is an options object, which can have a `waitForNode` property (either `boolean` or `number` - use a `number` to override the default timeout). NOTE: if you don't pass it, it defaults to `1000ms`.

When `waitForNode` is used, if the node does not exist yet, it will wait for the specified timeout and then update the data.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

**Example: Live data updates with DataSourceApi.updateData**

The DataSource has 10k items.

In this example, we're updating 5 rows (in the visible viewport) every 30ms.

The update rate could be much higher, but we're keeping it at current levels to make it easier to see the changes.

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

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

type Developer = {
  id: number;

  firstName: string;
  lastName: string;

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

  age: number;
  reposCount: number;
};

const dataSource = () => {
  return fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/developers10k-sql`)
    .then((r) => r.json())
    .then((data: Developer[]) => {
      return data;
    });
};

export function getRandomInt(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

const CURRENCIES = ['USD', 'CAD', 'EUR'];
const stacks = ['frontend', 'backend', 'fullstack'];

const updateRow = (api: DataSourceApi<Developer>, data: Developer) => {
  const getDelta = (num: number): number => Math.ceil(0.2 * num);

  const initialData = data;
  if (!initialData) {
    return;
  }
  const salaryDelta = getDelta(initialData?.salary);
  const reposCountDelta = getDelta(initialData?.reposCount);
  const newSalary =
    initialData.salary + getRandomInt(-salaryDelta, salaryDelta);
  const newReposCount =
    initialData.reposCount + getRandomInt(-reposCountDelta, reposCountDelta);

  const newData: Partial<Developer> = {
    id: initialData.id,
    salary: newSalary,
    reposCount: newReposCount,
    currency:
      CURRENCIES[getRandomInt(0, CURRENCIES.length - 1)] || CURRENCIES[0],
    stack: stacks[getRandomInt(0, stacks.length - 1)] || stacks[0],
    age: getRandomInt(0, 100),
  };

  api.updateData(newData);
};

let STARTED = false;

const ROWS_TO_UPDATE_PER_FRAME = 5;
const UPDATE_INTERVAL_MS = 30;

const randomlyUpdateData = ({
  api,
  dataSourceApi,
}: {
  api: InfiniteTableApi<Developer>;
  dataSourceApi: DataSourceApi<Developer>;
}) => {
  // protect for React.StrictMode potentially calling this twice
  if (STARTED) {
    return;
  }
  STARTED = true;
  setInterval(() => {
    const { renderStartIndex, renderEndIndex } = api.getVerticalRenderRange();
    const dataArray = dataSourceApi.getRowInfoArray();
    const data = dataArray
      .slice(renderStartIndex, renderEndIndex)
      .map((x) => x.data as Developer);

    for (let i = 0; i < ROWS_TO_UPDATE_PER_FRAME; i++) {
      const row = data[getRandomInt(0, data.length - 1)];
      if (row) {
        updateRow(dataSourceApi, row);
      }
    }
  }, UPDATE_INTERVAL_MS);
};

const columns: InfiniteTablePropColumns<Developer> = {
  id: {
    field: 'id',
  },
  firstName: {
    field: 'firstName',
  },
  age: {
    field: 'age',
    type: 'number',
    style: ({ value, rowInfo }) => {
      if (rowInfo.isGroupRow) {
        return {};
      }

      return {
        color: 'black',
        background:
          value > 80
            ? 'tomato'
            : value > 60
            ? 'orange'
            : value > 40
            ? 'yellow'
            : value > 20
            ? 'lightgreen'
            : 'green',
      };
    },
  },
  salary: {
    field: 'salary',
    type: 'number',
  },
  reposCount: {
    field: 'reposCount',
    type: 'number',
  },

  stack: { field: 'stack', renderMenuIcon: false },
  currency: { field: 'currency' },
};

const domProps = {
  style: {
    height: '100%',
  },
};
export default () => {
  return (
    <React.StrictMode>
      <DataSource<Developer> data={dataSource} primaryKey="id">
        <InfiniteTable<Developer>
          debugId="live-updates-example"
          domProps={domProps}
          onReady={randomlyUpdateData}
          columnDefaultWidth={130}
          columnMinWidth={50}
          columns={columns}
        />
      </DataSource>
    </React.StrictMode>
  );
};
```

### updateDataArray (`(data: Partial<DATA_TYPE>[], options?) => Promise`)

> Updates an array of data items to match the given data objects.

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

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

### onReady (`(api: DataSourceApi) => void`)

> Called only once, after the DataSource component has been mounted.

This callback prop will be called with an `DataSourceApi` instance. For retrieving the [`InfiniteTableApi`](https://infinite-table.com/docs/reference/api/index.md), see the `InfiniteTable` [`onReady`](https://infinite-table.com/docs/reference/infinite-table-props.md#onReady) callback prop.

### removeData (`(data: Partial<DATA_TYPE>) => Promise`)

> Removes the data item that matches the given data object.

The data object must at least have a primary key that matches the primary key of the data item that you want to remove. All the other properties are ignored.

For removing an array of data, see the [`removeDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataArray) method.

If you only want to remove by a primary key, you can call [`removeDataByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataByPrimaryKey) instead.
If you have an array of primary keys, you can call [`removeDataArrayByPrimaryKeys`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataArrayByPrimaryKeys) instead.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

### getOriginalDataArray (`() => DATA_TYPE[]`)

> Returns the data array that was last loaded by the `DataSource`

This is the array loaded by the `DataSource`, before any filtering, sorting or grouping is applied.

### removeDataArray (`(data: Partial<DATA_TYPE>[]) => Promise`)

> Removes the data items that match the given data objects.

The data objects must at least have a primary key that matches the primary key of the data item that you want to remove. All the other properties are ignored.

For removing only one item, see the [`removeData`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeData) method.

If you only want to remove by a primary key, you can call [`removeDataByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataByPrimaryKey) instead.
If you have an array of primary keys, you can call [`removeDataArrayByPrimaryKeys`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataArrayByPrimaryKeys) instead.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

### removeDataArrayByPrimaryKeys (`(primaryKeys: (string | number)[]) => Promise`)

> Removes the data items with the specified primary keys.

For removing only one data item, see the [`removeDataByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataByPrimaryKey) method.

If you have a data object, you can call [`removeData`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeData) instead.
If you have an array of data objects, you can call [`removeDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataArray) instead.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.

### removeDataByPrimaryKey (`(primaryKey: string | number) => Promise`)

> Removes the data item with the specified primary key.

For removing an array of data, see the [`removeDataArrayByPrimaryKeys`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataArrayByPrimaryKeys) method.

If you have a data object, you can call [`removeData`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeData) instead.
If you have an array of data objects, you can call [`removeDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataArray) instead.

[`onDataMutations`](https://infinite-table.com/docs/reference/datasource-props/index.md#onDataMutations) allows you to listen to data mutations.
