Skip to content
Pierre Wan-Fat edited this page Jun 23, 2020 · 5 revisions

Use the Table component to quickly display information about an array of objects into a table (such as the ones returned by the query functions). You can also configure it to support the sorting of the rows.

Simple table

On its simplest form, Table only requires a data prop containing an array of objects as well as a columns prop, an array of objects specifying the columns to be used and how to render the cells in that column.

An item in columns may have the following keys:

  • key (required): a unique key among the columns.
  • header (required): something to display on the first row of the table.
  • render: when rendering the cell located in the ith row and the current column, render is passed the ith item in data. If render is not specified, data[i][key] will be used to render instead.
  • sorting: a Sorting parameter. See the next section.
  • onChangeSorting: a callback when the sorting changes. See the next section.
  • headerClassName: given to the th element.
  • cellClassName: given to every td element.

Suppose we have a User interface:

interface User {
  name: string;
  age: number;
}

The following creates a two-column table where the first column displays the name of the person and the second column displays “Minor” or “Adult” depending on their age:

// Fetch it somehow.
users: User[] = ...

columns = [
  {
    key: "name",
    header: "Name",
  },
  {
    key: "type",
    header: "Type",
    render: (user) => user.age < 18 ? "Minor" : "Adult",
  }
];

return <Table data={users} columns={columns}/>;

Sorting

Each column in a Table may be in three states: unsorted, in ascending order or in descending order. These three states are represented by the Sorting enum (respectively Unsorted, Ascending and Descending.

As stated before, the columns items accept a sorting key which is undefined by default. Providing a state holding a Sorting value will cause an arrow to appear on the right of the header. Furthermore, the callback provided to onChangeSorting will be called when clicking on these arrows. It is thus possible to update the state given to sorting and it would look a bit like this.

// Fetch it somehow.
users: User[] = ...

const [nameSorting, setNameSorting] = useState(Sorting.Unsorted);

columns = [
  {
    key: "name",
    header: "Name",
    sorting: nameSorting,
    onChangeSorting: (newSorting) => setNameSorting(nameSorting),
  },
  {
    key: "type",
    header: "Type",
    render: (user) => user.age < 18 ? "Minor" : "Adult",
  }
];

return <Table data={users} columns={columns}/>;

Please note that Table does not modify your data, even when sorting it. In our example, you still have to use nameSorting wherever you fetch your data to change users accordingly.

Having to keep track of the Sorting states manually is a bit cumbersome. This is why useColumns was invented. This hook takes an array of objects which keys are the same as Column, except that instead of specifying sorting and onChangeSorting, you just specify a boolean canSort. If true, the hook will generate the corresponding states and callbacks. It will then return a Column[] object ready to be injected into the columns prop of Table. So the above example is equivalent to:

// Fetch it somehow.
users: User[] = ...

const { columns, sorting } = useColumns([
  {
    key: "name",
    header: "Name",
    canSort: true,
  },
  {
    key: "type",
    header: "Type",
    render: (user) => user.age < 18 ? "Minor" : "Adult",
  }
]);

return <Table data={users} columns={columns}/>;

Note that you also get a sorting object containing the Sorting states, which are meant to be used in the part of your code which fetches user. The next section describes how to use it with the classic REST API.

Link to the backend

Django REST Framework supports the ordering of the results (see OrderingFilter). Let's suppose that we fetch our User[] with an URL like /api/v1/users/?ordering=[param] where param is -name or name.

If using useBetterQuery, the code certainly looks something like this (we assume that api.users.list knows how to deal with keys which are compatible with toUrlParams).

const { data: users, error, status } = useBetterQuery(
  ["api.users.list", { get_parameter: some_value }],
  api.users.list
)

(...)

To integrate the sorting, just transform the sortings object with sortingToApiParameter:

const { columns, sorting } = useColumns([ ... ])

const { data: users, error, status } = useBetterQuery(
  [
    "api.users.list",
    {
      get_parameter: some_value,
      ordering: sortingToApiParameter(sorting),
    }
  ],
  api.users.list
)

(...)

And that's it! Now, every time you click on the header of the table, the states inside sorting are updated and the query is refetched.

Mapping

What is inside our sorting object exactly? If the object we pass to useColumns is the following:

[
  {
    key: "name",
    header: "Name",
    canSort: true,
  },
  {
    key: "type",
    header: "Type",
    render: (user) => user.age < 18 ? "Minor" : "Adult",
  }
]

then sorting will have one key, name, containing Sorting.Unsorted, Sorting.Ascending or Sorting.Descending. sortingToApiParameters will then transform this object into respectively undefined, name or -name (this is the very parameter which will be sent to the backend with the ordering key).

What if, for some reason, the GET parameter should not be name but something else like first_name or user__name? One solution would be to change the key in useColumns, but this is not always practical if you use the key to specify the property to render. However, you can also specify a mapping object to sortingToApiParameters:

sortingToApiParameter(
  sorting,
  {
    name: "user__name",
  }
)

Now, the GET request will be sent to /api/v1/users/?ordering=user__name.

Limitations

  • Multi-sorting is not supported by useColumns.