Multiple Sorting

By default, if you don't specify otherwise, the DataGrid is configured with single sorting. For multiple sorting, you need to specify the sorting information as an array:

<DataSource<Developer>
  primaryKey="id"
  data={data}
  // we want an array here
  defaultSortInfo={[]}
>
  <InfiniteTable<Developer> columns={columns} />
</DataSource>

Note

An empty array means no sorting. However, it does specify that sorting is configured as multiple sorting, so it's useful to set it to []

Configuring multiple sorting with uncontrolled behavior

Try clicking the age column and then the firstName column.

If the multi-sort behavior is replace, clicking the second column will remove the sort from the first column. In order for the sorting to be additive, even if the behavior is replace, use the Ctrl/Cmd key while clicking the column header.

If the multi-sort behavior is append, clicking the second column will add it to the sort.

View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  InfiniteTablePropMultiSortBehavior,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

type Developer = {
  id: number;
  firstName: string;
  country: string;
  city: string;
  currency: string;
  email: string;
  preferredLanguage: string;
  hobby: string;
  salary: number;
  age: number;
};

const columns: InfiniteTablePropColumns<Developer> = {
  firstName: { field: 'firstName', header: 'First Name' },
  age: { field: 'age', header: 'Age' },
  salary: {
    field: 'salary',
    header: 'Salary',
    type: 'number',
  },
  country: { field: 'country', header: 'Country' },
  preferredLanguage: { field: 'preferredLanguage' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function LocalUncontrolledSingleSortingExample() {
  const [multiSortBehavior, setMultiSortBehavior] = React.useState<
    'append' | 'replace'
  >('replace');
  return (
    <>
      <div
        style={{
          background: 'var(--infinite-background)',
          color: 'var(--infinite-cell-color)',
          display: 'flex',
          flexDirection: 'row',
        }}
      >
        <p style={{ padding: 10 }}>Select the multi-sort behavior</p>
        <select
          style={{
            margin: '10px 0',
            display: 'inline-block',
            background: 'var(--infinite-background)',
            color: 'var(--infinite-cell-color)',
            padding: 'var(--infinite-space-3)',
          }}
          value={multiSortBehavior}
          onChange={(event) => {
            const multiSortBehavior = event.target
              .value as InfiniteTablePropMultiSortBehavior;

            setMultiSortBehavior(multiSortBehavior);
          }}
        >
          <option value="replace">replace</option>
          <option value="append">append</option>
        </select>
      </div>
      <DataSource<Developer>
        primaryKey="id"
        data={dataSource}
        defaultSortInfo={[]}
      >
        <InfiniteTable<Developer>
          columns={columns}
          columnDefaultWidth={120}
          multiSortBehavior={multiSortBehavior}
        />
      </DataSource>
    </>
  );
}

const dataSource: Developer[] = [
  {
    id: 0,
    firstName: 'Nya',
    country: 'India',
    city: 'Unnao',
    age: 24,
    currency: 'JPY',
    preferredLanguage: 'TypeScript',
    salary: 60000,
    hobby: 'sports',
    email: 'Nya44@gmail.com',
  },
  {
    id: 1,
    firstName: 'Axel',
    country: 'Mexico',
    city: 'Cuitlahuac',
    age: 46,
    currency: 'USD',
    preferredLanguage: 'TypeScript',
    salary: 100000,
    hobby: 'sports',
    email: 'Axel93@hotmail.com',
  },
  {
    id: 2,
    firstName: 'Gonzalo',
    country: 'United Arab Emirates',
    city: 'Fujairah',
    age: 24,
    currency: 'JPY',
    preferredLanguage: 'Go',
    salary: 120000,
    hobby: 'photography',
    email: 'Gonzalo_McGlynn34@gmail.com',
  },
  {
    id: 3,
    firstName: 'Sherwood',
    country: 'Mexico',
    city: 'Tlacolula de Matamoros',
    age: 24,
    currency: 'CHF',
    preferredLanguage: 'Rust',
    salary: 99000,
    hobby: 'cooking',
    email: 'Sherwood_McLaughlin65@hotmail.com',
  },
  {
    id: 4,
    firstName: 'Alexandre',
    country: 'France',
    city: 'Persan',
    age: 24,
    currency: 'EUR',
    preferredLanguage: 'Go',
    salary: 97000,
    hobby: 'reading',
    email: 'Alexandre_Harber@hotmail.com',
  },
  {
    id: 5,
    firstName: 'Mariane',
    country: 'United States',
    city: 'Hays',
    age: 23,
    currency: 'EUR',
    preferredLanguage: 'TypeScript',

    salary: 58000,
    hobby: 'cooking',
    email: 'Mariane0@hotmail.com',
  },
  {
    id: 6,
    firstName: 'Rosalind',
    country: 'Mexico',
    city: 'Nuevo Casas Grandes',
    age: 23,
    currency: 'AUD',
    preferredLanguage: 'JavaScript',
    salary: 198000,
    hobby: 'dancing',
    email: 'Rosalind69@gmail.com',
  },
  {
    id: 7,
    firstName: 'Lolita',
    country: 'Sweden',
    city: 'Delsbo',
    age: 22,
    currency: 'JPY',
    preferredLanguage: 'TypeScript',
    salary: 200000,
    hobby: 'cooking',
    email: 'Lolita.Hayes@hotmail.com',
  },
  {
    id: 8,
    firstName: 'Tre',
    country: 'Germany',
    city: 'Bad Camberg',
    age: 23,
    currency: 'GBP',
    preferredLanguage: 'TypeScript',
    salary: 200000,
    hobby: 'sports',
    email: 'Tre28@gmail.com',
  },
  {
    id

User interaction and multi sort behavior

When InfiniteTable is configured with multiple sorting there are two supported behaviors:

  • append - when this behavior is used, clicking a column header adds that column to the alredy existing sort. If the column is already sorted, the sort direction is reversed. In order to remove a column from the sort, the user needs to click the column header in order to toggle sorting from ascending to descending and then to no sorting.
  • replace - the default behavior - a user clicking a column header removes any existing sorting and sets that column as sorted. In order to add a new column to the sort, the user needs to hold the Ctrl/Cmd key while clicking the column header.

Note

The behavior of multiple sorting is configured via the multiSortBehavior - the default value for this prop is "replace".

❗️❗️❗️ The multiSortBehavior prop is defined on the InfiniteTable component, not on the DataSource component - as it's the InfiniteTable that handles user interaction, even though the DataSource does the actual sorting.

Multi sort behavior - append

Scenario 1

  • user clicks a column header to sort by that column - an ascending sort is added, and the column header will contain the sort index - 1
  • if user clicks the same column, the sort direction is reversed - sort index is preserved as 1, but descending order is set.
  • user clicks the same column again - the column is removed from the sort.

Scenario 2

  • user clicks a column header to sort by that column - an ascending sort is added, and the column header will contain the sort index - 1
  • user clicks another column - the new column is added to the sort, with ascending order and sort index 2. The initial clicked column is still the sorted, and that sort is applied first. For equal values on column 1, the sort by column 2 is applied.
  • user clicks column 2 again - the sort direction is reversed for the second column. So now the sort order is 1 ascending, 2 descending.
  • user clicks column 2 again - the column is removed from the sort. The sorting now only contains the first column, in ascending order.

Multi sort behavior - replace

Note

This is the default behavior for multiple sorting.

In the replace behavior, clicking a column header will remove any existing sorting and set that specific column as sorted.

In order to add a new column to the sort, the user needs to hold the Ctrl/Cmd key while clicking a column header. Holding the Ctrl/Cmd key while clicking a column header results in the same behavior as the append.

Controlled and uncontrolled sorting

As noted above, for multiple sorting, you need to specify an array of objects - see DataSourceSingleSortInfo for more on the shape of those objects:

// sort by age in descending order, then by `firstName` in ascending order
sortInfo = [
  { field: 'age', type: 'number', dir: -1 },
  { field: 'firstName', dir: 1 },
];

// no sorting
sortInfo = [];

The simplest way to use multiple sorting is via the uncontrolled defaultSortInfo prop. Specify an empty array as the default value, and multiple sorting will be enabled.

This allows sorting by multiple fields (to which columns are bound) - you can specify however many you want - so when sorting two objects in the DataSource, the first sortInfo is used to compare the two, and then, on equal values, the next sortInfo is used and so on.

Note

If you want to change the sorting from code, after the component is mounted, you need to use the controlled sortInfo prop.

In this case, make sure you update the sortInfo prop as a result of user interaction, by using the onSortInfoChange callback.

Local + uncontrolled multi-sorting example

This table allows sorting multiple columns - initially the country column is sorted in descending order and the salary column is sorted in ascending order. Ctrl/Cmd + click the salary column to toggle the column sort to descending. Ctrl/Cmd clicking it a second time will remove it from the sort altogether.

View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  country: string;
  city: string;
  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';
  hobby: string;
  salary: number;
  age: number;
};

const dataSource: DataSourceData<Developer> = () => {
  return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers100-sql?`)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  firstName: { field: 'firstName' },

  country: { field: 'country' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  id: { field: 'id' },
  canDesign: { field: 'canDesign' },
  preferredLanguage: { field: 'preferredLanguage' },
  stack: { field: 'stack' },

  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

const domProps = { style: { height: '90vh' } };

export default function LocalUncontrolledMultiSortingExampleWithRemoteData() {
  return (
    <>
      <DataSource<Developer>
        primaryKey="id"
        data={dataSource}
        defaultSortInfo={[
          { field: 'country', dir: -1 },
          { field: 'salary', dir: 1 },
        ]}
        sortMode="local"
      >
        <InfiniteTable<Developer>
          domProps={domProps}
          columns={columns}
          columnDefaultWidth={120}
        />
      </DataSource>
    </>

Remote + uncontrolled multi-sorting example
View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  country: string;
  city: string;
  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';
  hobby: string;
  salary: number;
  age: number;
};

const dataSource: DataSourceData<Developer> = ({ sortInfo }) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers100-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  country: { field: 'country' },
  preferredLanguage: { field: 'preferredLanguage' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function RemoteUncontrolledMultiSortingExample() {
  return (
    <>
      <DataSource<Developer>
        primaryKey="id"
        data={dataSource}
        defaultSortInfo={[
          {
            field: 'salary',
            dir: -1,
          },
        ]}
        sortMode="remote"
      >
        <InfiniteTable<Developer> columns={columns} columnDefaultWidth={120} />
      </DataSource>
    </>

Note

If you use uncontrolled sorting via defaultSortInfo there's no way to switch between single and multiple sorting after the component is mounted. If you have this use-case, you need to use the controlled sortInfo prop.

Remote Sorting

Sorting remotely makes a lot of sense when using a function as your data source. Whenever the sort information is changed, the function will be called with all the information needed to retrieve the data from the remote endpoint.

Note

For remote sorting, make sure you specify sortMode="remote" - if you don't, the data will also be sorted locally in the browser (which most of the times will be harmless, but it means wasted CPU cycles).

Remote + controlled multi-sorting example
View Mode
Fork
import {
  InfiniteTable,
  DataSource,
  DataSourceData,
  DataSourcePropSortInfo,
} from '@infinite-table/infinite-react';
import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';
import * as React from 'react';

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  country: string;
  city: string;
  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';
  hobby: string;
  salary: number;
  age: number;
};

const dataSource: DataSourceData<Developer> = ({ sortInfo }) => {
  if (sortInfo && !Array.isArray(sortInfo)) {
    sortInfo = [sortInfo];
  }
  const args = [
    sortInfo
      ? 'sortInfo=' +
        JSON.stringify(
          sortInfo.map((s) => ({
            field: s.field,
            dir: s.dir,
          })),
        )
      : null,
  ]
    .filter(Boolean)
    .join('&');

  return fetch('https://infinite-table.com/.netlify/functions/json-server' + `/developers100-sql?` + args)
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};

const columns: InfiniteTablePropColumns<Developer> = {
  preferredLanguage: { field: 'preferredLanguage' },
  salary: {
    field: 'salary',
    type: 'number',
  },
  age: { field: 'age' },
  canDesign: { field: 'canDesign' },
  country: { field: 'country' },
  firstName: { field: 'firstName' },
  stack: { field: 'stack' },
  id: { field: 'id' },
  hobby: { field: 'hobby' },
  city: { field: 'city' },
  currency: { field: 'currency' },
};

export default function RemoteControlledMultiSortingExample() {
  const [sortInfo, setSortInfo] = React.useState<
    DataSourcePropSortInfo<Developer>
  >([
    {
      field: 'salary',
      dir: -1,
    },
  ]);
  return (
    <>
      <DataSource<Developer>
        primaryKey="id"
        data={dataSource}
        sortInfo={sortInfo}
        sortMode="remote"
        onSortInfoChange={setSortInfo}
      >
        <InfiniteTable<Developer> columns={columns} columnDefaultWidth={220} />
      </DataSource>
    </>

In the example above, remote and controlled sorting are combined - because sortMode="remote" is specified, the <DataSource /> will call the data function whenever sorting changes, and will pass in the dataParams object that contains the sort information.