# Real-time data updates in your React DataGrid

> Use the Infinite Table DataSource API to stream row updates, batch changes, and keep large datasets responsive.

Published: 2026-07-07  
Author: radu  
Tags: realtime, datasource, performance

Canonical page: https://infinite-table.com/blog/2026/07/07/real-time-data-updates-with-the-datasource-api

Most data grids are not showing static data. Dashboards receive fresh metrics, trading screens react to price changes, logistics apps track moving assets, and admin tools often need to reflect edits made by other users.

Infinite Table already has a [dedicated for for updating data in real time](https://infinite-table.com/docs/learn/working-with-data/updating-data-in-realtime.md), and this article adds more guide on how you can update the `DataSource`, not the whole grid.

## The DataSource API owns row updates

The `DataSource` component is responsible for loading, processing, and preparing data for `<InfiniteTable />`. When you need to change rows after the grid is mounted, use the [DataSource API](https://infinite-table.com/docs/reference/datasource-api/index.md).

The `<InfiniteTable />` component doesn't need to be a direct child of the `<DataSource />`

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

```tsx
const onReady = (dataSourceApi) => {
  // store the dataSourceApi and use it when updates arrive
};

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

Or from `InfiniteTable.onReady`, where you receive both the grid API and the DataSource API:

```tsx
const onReady = ({ api, dataSourceApi }) => {
  // api controls grid behavior
  // dataSourceApi controls data updates
};

<DataSource primaryKey="id" data={data}>
  <InfiniteTable onReady={onReady} />
</DataSource>;
```

## Update one row by primary key

To update a row, call `dataSourceApi.updateData` with an object that includes the configured [`primaryKey`](https://infinite-table.com/docs/reference/datasource-props/index.md#primaryKey) field. Any other fields you include are merged into the existing row data.

```tsx {1,3}
dataSourceApi.updateData({
  id: 42,
  salary: 124000,
  currency: 'USD',
  reposCount: 37,
});
```

This keeps the update local to the row data that changed. You do not need to rebuild the whole array just because one value moved.

## Update many rows without creating render noise

When several rows change together, use `dataSourceApi.updateDataArray`.

```tsx {1,3,9}
dataSourceApi.updateDataArray([
  {
    id: 42,
    salary: 124000,
    currency: 'USD',
  },
  {
    id: 73,
    salary: 118500,
    currency: 'EUR',
  },
]);
```

The docs call out an important implementation detail: DataSource row mutations are batched by default. Multiple insert, update, or delete calls made in the same `requestAnimationFrame` resolve through the same promise and trigger one render pass.

```tsx
const firstUpdate = dataSourceApi.updateData({
  id: 1,
  salary: 115000,
});

const secondUpdate = dataSourceApi.updateData({
  id: 2,
  salary: 99000,
});

firstUpdate === secondUpdate; // true
```

That batching matters when updates are frequent. It lets your app react to a stream of changes while Infinite Table keeps the rendering work grouped.

## See it with 10k rows

The live demo below uses the same example from the [Live Updates docs page](https://infinite-table.com/docs/learn/examples/live-updates-example.md). It loads 10k rows and, when started, updates five rows from the visible viewport every 30ms.

**Example: Real-time DataSource updates with 10k rows**

Click **Start updates** to update visible rows in real time. The example uses the DataSource API to change individual rows without replacing the whole dataset.

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

## When to use this pattern

Reach for the DataSource API when data changes after initial load:

- websocket or Server-Sent Events feeds
- polling that returns changed records
- optimistic updates after user edits
- background imports that append or remove rows
- dashboards where visible values change continuously

For simple local state, replacing the `data` prop can be fine. For ongoing row-level changes, the DataSource API gives you a clearer contract: every mutation includes the primary key, and Infinite Table handles the data update pipeline.

Start with the docs page on [updating data in real time](https://infinite-table.com/docs/learn/working-with-data/updating-data-in-realtime.md), then open the live updates example and adapt the update loop to your app's data source.
