Working with Data

We’ve decoupled our data handling from our rendering engine by providing two components (as named exports) inside @infinite-table/infinite-react package:

  • <DataSource /> - our data-handling component
  • <InfiniteTable /> - our virtualized component

The <DataSource/> component is responsible for the data the management layer. It is a generic TypeScript component that can be bound to an array of items of the generic type.

import { DataSource } from '@infinite-table/infinite-react';

type Employee = {
  id: string | number;
  name: string;
  salary: number;
  department: string;
  company: string;
}

const employees: Employee[] = [
  {id: 1, name: 'Bob', salary: 10_000, department: 'IT', company: 'Bobsons'},
  {id: 2, name: 'Alice', salary: 20_000, department: 'IT', company: 'Bobsons'},
  {id: 3, name: 'John', salary: 30_000, department: 'IT', company: 'Bobsons'}
];

<DataSource<Employee> primaryKey={"id"} data={employees} />

In the snippet above, we see 3 important details:

  • the component is bound to the Employee type
  • we use a primaryKey property - here it is id, but since the bound type is Employee, primaryKey is keyof Employee
  • we pass the employees array as the data property.

The data prop is probably the most important part, and it can be one of:

  • an array of the bound type - here Employee[]
  • a Promise tha resolves to an array like the above
  • a function that returns an any of the above
Data loading example with promise
Fork
import * as React from 'react';
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
} from '@infinite-table/infinite-react';

type Employee = {
  id: string | number;
  name: string;
  salary: number;
  department: string;
  company: string;
};

const employees: Employee[] = [
  {
    id: 1,
    name: 'Bob',
    salary: 10_000,
    department: 'IT',
    company: 'Bobsons',
  },
  {
    id: 2,
    name: 'Alice',
    salary: 20_000,
    department: 'IT',
    company: 'Bobsons',
  },
  {
    id: 3,
    name: 'John',
    salary: 30_000,
    department: 'IT',
    company: 'Bobsons',
  },
  {
    id: 4,
    name: 'Jane',
    salary: 35_000,
    department: 'Marketing',
    company: 'Janies',
  },
  {
    id: 5,
    name: 'Mary',
    salary: 40_000,
    department: 'Marketing',
    company: 'Janies',
  },
];

// simulate data-loading with a 500ms delay
const data = new Promise<Employee[]>((resolve) => {
  setTimeout(() => {
    resolve(employees);
  }, 1000);
});

export default function App() {
  return (
    <DataSource<Employee> data={data} primaryKey="id">
      <InfiniteTable<Employee>
        columnDefaultWidth={130}
        columns={columns}
      />
    </DataSource>
  );
}

const columns: Record<
  string,
  InfiniteTableColumn<Employee>
> = {
  id: {
    field: 'id',
    type: 'number',
    defaultWidth: 80,
  },
  name: {
    field: 'name',
  },
  salary: { field: 'salary', type: 'number' },
  department: { field: 'department', header: 'Dep.' },
  company: { field: 'company' },
};

Data loading strategies

We’re aware there are countless strategies for loading data - each with its own strengths. So we decided we should focus on building what we do best, namely building virtualized components.

So we encourage you to use your preferred data-fetching library/solution and pass a dumb array of data to the <DataSource/> component. And while you’re loading the data, you can always render a loading indicator - pass the loading prop into the component (along with loadingText prop in the <InfiniteTable /> component if you want to customize the message).

Using fetch

For basic datasets, which have simple data requirements, using fetch is probably enough, so here’s an example:

Using fetch for remote data
Fork
import * as React from 'react';
import {
  InfiniteTable,
  DataSource,
} from '@infinite-table/infinite-react';

import { columns, Employee } from './columns';

export default function App() {
  return (
    <DataSource<Employee> data={dataSource} primaryKey="id">
      <InfiniteTable<Employee> columns={columns} />
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(
    'https://infinite-table.com/.netlify/functions/json-server' + '/employees10k'
  )
    .then((r) => r.json())
    .then((data: Employee[]) => {
      return data;
    });
};

Refetching on change

Note

It’s important to note you can re-fetch data by changing the reference you pass as the data prop to the <DataSource/> component. Passing another data function, will cause the component to re-execute the function and thus load new data.

Re-fetching data
Fork
import * as React from 'react';
import {
  InfiniteTable,
  DataSource,
} from '@infinite-table/infinite-react';

import { columns, Employee } from './columns';

const getDataSourceFor = (size: string) => {
  if (size === '0') {
    return () => Promise.resolve([]);
  }
  return () => {
    return fetch(
      'https://infinite-table.com/.netlify/functions/json-server' + '/employees' + size
    )
      .then((r) => r.json())
      .then((data: Employee[]) => data);
  };
};

export default function App() {
  const [dataSourceSize, setDataSourceSize] =
    React.useState<string>('10');

  const dataSource = React.useMemo(() => {
    return getDataSourceFor(dataSourceSize);
  }, [dataSourceSize]);
  return (
    <div
      style={{
        display: 'flex',
        flex: 1,
        color: 'var(--infinite-row-color)',
        flexFlow: 'column',
        background: 'var(--infinite-background)',
      }}>
      <p style={{ padding: 10 }}>
        Please select the size of the datasource:
      </p>
      <div style={{ padding: 10 }}>
        <select
          style={{
            margin: '10px 0',
            display: 'inline-block',
            background: 'var(--infinite-background)',
            color: 'currentColor',
            padding: 'var(--infinite-space-3)',
          }}
          value={dataSourceSize}
          onChange={(event) => {
            const newSize = event.target.value as string;

            setDataSourceSize(newSize);
          }}>
          <option value="0">no items</option>
          <option value="10">10 items</option>
          <option value="100">100 items</option>
          <option value="1k">1k items</option>
          <option value="10k">10k items</option>
        </select>
      </div>
      <DataSource<Employee>
        data={dataSource}
        primaryKey="id">
        <InfiniteTable<Employee>
          columns={columns}
          columnDefaultWidth={150}
        />
      </DataSource>
    </div>
  );
}

Live updates

We’ll come up with a more suitable API for updating only specific cells/rows in the table component.