Table
A table is a specific pattern for comparing datasets in a very direct and analytical way.
install | yarn add @clayui/core |
---|---|
version | 3.120.0 |
Beta3.120.0View in LexiconCHANGELOG
Example
Introduction
Table allows the rendering of static and dynamic content for data-oriented and data-agnostic tables to prevent the developer from having to transform their data to be able to render in a table. Composition is the central point, as is the use of the render props pattern to allow this so that the component can offer OOTB features, such as sorting and nested row.
The component covers the W3C accessibility patterns for the simplest table implementation and also the treegrid pattern for when nested row is used without requiring the developer to have to configure something extremely complex.
@clayui/core
.Content
Content rendered in the <Table />
Menu can be done in two different ways, static and dynamic content, the choice depends on the use case.
Static
Static content is when the <Table />
options do not change during the lifecycle of the application or are hardcoded options.
<Table>
<Head>
<Cell key="name">Name</Cell>
<Cell key="type">Type</Cell>
</Head>
<Body>
<Row>
<Cell>Games</Cell>
<Cell>File Folder</Cell>
</Row>
<Row>
<Cell>Program Files</Cell>
<Cell>File Folder</Cell>
</Row>
</Body>
</Table>
Dynamic
Unlike static content, dynamic content is when the options can change during the lifecycle of the application or when the data comes from a service. Dynamic content rendering is data agnostic, this allows you to configure how to render the component options regardless of the chosen data structure. For more information about controlled and uncontrolled components, visit this blog post.
<Table>
<Head
items={[
{
id: '1',
name: 'Name',
},
{
id: '2',
name: 'Type',
},
]}
>
{(column) => <Cell key={column.id}>{column.name}</Cell>}
</Head>
<Body
items={[
{id: 1, name: 'Games', type: 'File folder'},
{id: 2, name: 'Program Files', type: 'File folder'},
]}
onItemsChange={() => {
// do something
}}
>
{(row) => (
<Row>
<Cell>{row.name}</Cell>
<Cell>{row.type}</Cell>
</Row>
)}
</Body>
</Table>
Icons
import {Provider} from '@clayui/core';
const spritemap = 'icons.svg';
<Provider spritemap={spritemap}>
<Table>
<Head
items={[
{
id: '1',
name: 'Name',
},
{
id: '2',
name: 'Type',
},
]}
>
{(column) => <Cell key={column.id}>{column.name}</Cell>}
</Head>
<Body
defaultItems={[
{id: 1, name: 'Games', type: 'File folder'},
{id: 2, name: 'Program Files', type: 'File folder'},
]}
>
{(row) => (
<Row>
<Cell>{row.name}</Cell>
<Cell>{row.type}</Cell>
</Row>
)}
</Body>
</Table>
</Provider>;
Sorting
Column sorting is implemented OOTB so the developer doesn't need to worry about implementing the UI details but the developer still needs to add their filter layer since the component is data-agnostic and allows you to do this asynchronously, it is important, especially when your data is paged, that the filter must happen in the backend.
It is also possible to implement your own logic on the client side when your data is predictable, check out the pseudocode.
sortable
API defined.const [sort, setSort] = useState<Sorting | null>(null);
const [items, setItems] = useState([
{files: 22, id: 1, name: 'Games', type: 'File folder'},
{files: 7, id: 2, name: 'Program Files', type: 'File folder'},
]);
const filteredItems = useMemo(() => {
if (!sort) {
return;
}
return items.sort((a, b) => {
let cmp = new Intl.Collator('en', {numeric: true}).compare(
a[sort.column],
b[sort.column]
);
if (sort.direction === 'descending') {
cmp *= -1;
}
return cmp;
});
}, [sort, items])
<Table onSortChange={setSort} sort={sort}>
<Head>
<Cell key="name" sortable>
Name
</Cell>
<Cell key="files" sortable>
Files
</Cell>
<Cell key="type" sortable>
Type
</Cell>
</Head>
<Body defaultItems={filteredItems}>
...
</Body>
</Table>
Asynchronous
Most tables with sorting can have a lot of data and are paged so the sorting must happen on the server side instead of implementing the logic on the client side. You can achieve this level of implementation by composing using the useResource
hook.
const {
resource: items,
sort,
sortChange,
} = useResource({
fetch: (link, init, sort) => {
const url = new URL(link);
if (sort) {
url.searchParams.append('column', String(sort.column));
url.searchParams.append('direction', sort.direction);
}
return fetch(url, init);
},
link: 'https://example.com/api/items',
});
<Table onSortChange={setSort} sort={sort}>
<Head>
<Cell key="name" sortable>
Name
</Cell>
<Cell key="files" sortable>
Files
</Cell>
<Cell key="type">Type</Cell>
</Head>
<Body defaultItems={items}>...</Body>
</Table>;
Nested row
Implementing nested row allows you to render a table as a tree view. It is not necessary that you have to change your composition to render in tree view but just configure the nestedKey
property to inform which nested key and the composition can continue in the same way.
When using the nested row pattern, Clay automatically changes the accessibility behavior to use the treegrid recommendation instead of the default behavior.
key
in the Row
component property to deal with the nodes expandability, it is necessary that the id
key is defined in your row data to use as unique id.Expandable
Expanding nodes is done OOTB in the component but it is also possible to control the state to modify behaviors if necessary or use it to save a session to improve the user experience.
const [expandedKeys, setExpandedKeys] = useState(new Set());
<Table expandedKeys={expandedKeys} onExpandedChange={setExpandedKeys}>
<Head items={columns}>
{(column) => <Cell key={column.id}>{column.name}</Cell>}
</Head>
<Body defaultItems={rows}>
{(row) => (
<Row items={columns}>
{(column) => <Cell>{row[column.id]}</Cell>}
</Row>
)}
</Body>
</Table>;
Asynchronous Item
When the tree is very large with a lot of data on a single node, loading the data asynchronously is essential to reduce the initial data payload and memory space. Table supports asynchronous node loading when the user expands a node.
- When returning
void
,null
orundefined
the Table will do nothing. - When returning the
items
will add to the tree.
onLoadMore
method, only the suppression is done and an error is thrown on the console.When adding a new asynchronous item to the tree, the onItemsChange
method is respectively called to update the tree with a new value if the items
prop is controlled.
<Table
onLoadMore={async (item) => {
return await fetch(`example.com/tree/item?parent_id=${item.id}`);
}}
>
<Head items={columns}>
{(column) => <Cell key={column.id}>{column.name}</Cell>}
</Head>
<Body
defaultItems={[
{id: 1, name: 'Games', type: 'File folder'},
{id: 2, name: 'Program Files', type: 'File folder'},
]}
>
{(row) => (
<Row>
<Cell>{row.name}</Cell>
<Cell>{row.type}</Cell>
</Row>
)}
</Body>
</Table>