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, noprimaryKeyis 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, theprimaryKeyis 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: