Updating Data in Real-Time
Real-Time updates of data are possible via the DataSource API.
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 for this.You can get a reference to the DataSource API
- either by using the DataSource onReady prop
const onReady = (dataSourceApi) => { // do something with the dataSourceApi }; <DataSource onReady={onReady} />;
COPY
- or by using the InfiniteTable onReady prop.
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>
COPY
Updating Rows#
To update the data of a row, you need to know the
primaryKey
for that row and use the updateData
method of the DataSource API. 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, });
COPY
To update multiple rows, you need to pass the array of data items to the
updateDataArray
method. 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, }, ]);
COPY
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.
View Mode
Fork Forkimport * 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(`${'https://infinite-table.com/.netlify/functions/json-server'}/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> domProps={domProps} onReady={onReady} columnDefaultWidth={130} columnMinWidth={50} columns={columns} /> </DataSource> </React.StrictMode>
For updating multiple rows, use the
updateDataArray
method.When updating a row, the data object you pass to the
updateData
method needs to at least include the 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 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
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
COPY
Inserting Rows#
To insert a new row into the
DataSource
, you need to use the insertData
method. For inserting multiple rows at once, use the insertDataArray
method. Inserting_a_single_row
dataSourceApi.insertData( { id: 10, name: 'Bob Blue', age: 35, salary: 12_000, stack: 'frontend', //... }, { position: 'before', primaryKey: 2, }, );
COPY
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, noprimaryKey
is needed.
dataSourceApi.insertData({ ... }, { position: 'start'}) // or insert multiple items via dataSourceApi.insertDataArray([{ ... }, { ... }], { position: 'start'})
COPY
before
|after
- inserts the data before or after the data item that has the specified primary key. In thise case, theprimaryKey
is required.
dataSourceApi.insertData( { /* ... all data properties here */ }, { position: 'before', primaryKey: 2 } ) // or insert multiple items via dataSourceApi.insertDataArray([{ ... }, { ... }], { position: 'after', primaryKey: 10 })
COPY
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.
View Mode
Fork Forkimport * 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> 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
and insertDataArray
methods, the DataSource
also exposes the addData
and 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: