# Updating Data in Real-Time

Canonical page: https://infinite-table.com/docs/learn/working-with-data/updating-data-in-realtime

Real-Time updates of data are possible via the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md).

In this page we explain some of the complexities and features involved.

## Getting a reference to the DataSource API

Data Updates are related to the `DataSource` component, therefore make sure you use the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md) for this.

You can get a reference to the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md)

- either by using [the DataSource onReady](https://infinite-table.com/docs/reference/datasource-props/index.md#onReady) prop

```tsx
const onReady = (dataSourceApi) => {
  // do something with the dataSourceApi
};

<DataSource onReady={onReady} />;
```

- or by using the [InfiniteTable onReady](https://infinite-table.com/docs/reference/infinite-table-props.md#onReady) prop.

```tsx
const onReady = ({ api, dataSourceApi }) => {
  // note for InfiniteTable.onReady, you get back an object
  // with both the InfiniteTable API (the `api` property)
  // and the DataSource API (the `dataSourceApi` property)
}

<DataSource {...}>
  <InfiniteTable onReady={onReady}/>
</DataSource>
```

## Updating Rows

To update the data of a row, you need to know the `primaryKey` for that row and use the [`updateData`](https://infinite-table.com/docs/reference/datasource-api/index.md#updateData) method of the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md).

```tsx {1,3} title="Updating_a_single_row_using_dataSourceApi.updateData"
dataSourceApi.updateData({
  // if the primaryKey is the "id" field, make sure to include it
  id: 1,

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

To update multiple rows, you need to pass the array of data items to the [`updateDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#updateDataArray) method.

```tsx {1,3,8} title="Updating_multiple_rows"
dataSourceApi.updateDataArray([
  {
    id: 1, // if the primaryKey is the "id" field, make sure to include it
    name: 'Bob Blue',
    age: 35,
  },
  {
    id: 2, // primaryKey for this row
    name: 'Alice Green',
    age: 25,
  },
]);
```

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

The DataSource has 10k items - use the **Start/Stop** button to see updates in real-time.

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 '@infinite-table/infinite-react/index.css';
import {
  DataSourceApi,
  InfiniteTable,
  InfiniteTableApi,
  InfiniteTablePropColumns,
  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);
};

const ROWS_TO_UPDATE_PER_FRAME = 5;
const UPDATE_INTERVAL_MS = 30;

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%',
  },
};
export default function App() {
  const [running, setRunning] = React.useState(false);

  const [apis, onReady] = React.useState<{
    api: InfiniteTableApi<Developer>;
    dataSourceApi: DataSourceApi<Developer>;
  }>();

  const intervalIdRef = React.useRef<any>(null);

  React.useEffect(() => {
    const { current: intervalId } = intervalIdRef;

    if (!running || !apis) {
      return clearInterval(intervalId);
    }

    intervalIdRef.current = setInterval(() => {
      const { dataSourceApi, api } = apis!;
      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);
        }
      }

      return () => {
        clearInterval(intervalIdRef.current);
        intervalIdRef.current = null;
      };
    }, UPDATE_INTERVAL_MS);
  }, [running, apis]);

  return (
    <React.StrictMode>
      <button
        style={{
          border: '2px solid var(--infinite-cell-color)',
          borderRadius: 10,
          padding: 10,
          background: running ? 'tomato' : 'var(--infinite-background)',
          color: running ? 'white' : 'var(--infinite-cell-color)',
          margin: 10,
        }}
        onClick={() => {
          setRunning(!running);
        }}
      >
        {running ? 'Stop' : 'Start'} updates
      </button>
      <DataSource<Developer> data={dataSource} primaryKey="id">
        <InfiniteTable<Developer>
          debugId="realtime-updates-example"
          domProps={domProps}
          onReady={onReady}
          columnDefaultWidth={130}
          columnMinWidth={50}
          columns={columns}
        />
      </DataSource>
    </React.StrictMode>
  );
}
```

For updating multiple rows, use the [`updateDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#updateDataArray) method.

When updating a row, the data object you pass to the `updateData` method needs to at least include the [`primaryKey`](https://infinite-table.com/docs/reference/datasource-props/index.md#primaryKey) field. Besides that field, it can include any number of properties you want to update for the specific row.

## Batching updates

All the methods for updating/inserting/deleting rows exposed via the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md) are batched by default. So you can call multiple methods on the same raf (requestAnimationFrame), and they will trigger a single render.

All the function calls made in the same raf return the same promise, which is resolved when the data is persisted to the `DataSource`

```tsx title="Updates_made_on_the_same_raf_are_batched_together"
const promise1 = dataSourceApi.updateData({
  id: 1,
  name: 'Bob Blue',
});

const promise2 = dataSourceApi.updateDataArray([
  { id: 2, name: 'Alice Green' },
  { id: 3, name: 'John Red' },
]);

promise1 === promise2; // true
```

## Inserting Rows

To insert a new row into the `DataSource`, you need to use the [`insertData`](https://infinite-table.com/docs/reference/datasource-api/index.md#insertData) method. For inserting multiple rows at once, use the [`insertDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#insertDataArray) method.

```tsx title="Inserting_a_single_row"
dataSourceApi.insertData(
  {
    id: 10,
    name: 'Bob Blue',
    age: 35,
    salary: 12_000,
    stack: 'frontend',
    //...
  },
  {
    position: 'before',
    primaryKey: 2,
  },
);
```

When you insert new data, as a second parameter, you have to provide an object that specifies the insert `position`.

Valid values for the insert `position` are:

- `start` | `end` - inserts the data at the beginning or end of the data source. In this case, no `primaryKey` is needed.

```tsx
dataSourceApi.insertData({ ... }, { position: 'start'})
// or insert multiple items via
dataSourceApi.insertDataArray([{ ... }, { ... }], { position: 'start'})
```

- `before` | `after` - inserts the data before or after the data item that has the specified primary key. **In thise case, the `primaryKey` is required.**

```tsx {5,10}
dataSourceApi.insertData(
  { /* ... all data properties here */ },
  {
    position: 'before',
    primaryKey: 2
  }
)
// or insert multiple items via
dataSourceApi.insertDataArray([{ ... }, { ... }], {
  position: 'after',
  primaryKey: 10
})
```

**Example: Using dataSourceApi.insertData**

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 '@infinite-table/infinite-react/index.css';
import {
  DataSourceApi,
  InfiniteTable,
  InfiniteTableApi,
  InfiniteTablePropColumns,
  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) => {
            if (apis) {
              const id = apis.dataSourceApi.getRowInfoArray()[rowIndex].id;

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

### Adding rows

In addition to the [`insertData`](https://infinite-table.com/docs/reference/datasource-api/index.md#insertData) and [`insertDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#insertDataArray) methods, the `DataSource` also exposes the [`addData`](https://infinite-table.com/docs/reference/datasource-api/index.md#addData) and [`addDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#addDataArray) methods (same as insert with `position=end`).

## Deleting Rows

To delete rows from the `DataSource` you either need to know the `primaryKey` for the row you want to delete, or you can pass the data object (or at least a partial that contains the `primaryKey`) for the row you want to delete.

All the following methods are available via the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md):

- [`removeData`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeData)
- [`removeDataArray`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataArray)
- [`removeDataByPrimaryKey`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataByPrimaryKey)
- [`removeDataArrayByPrimaryKeys`](https://infinite-table.com/docs/reference/datasource-api/index.md#removeDataArrayByPrimaryKeys)
