I am currently trying to implement React-Table, with a data structure which matches this typescript definition.
export type VendorContent = {
id: number;
name: string;
user_name: string;
dob: string;
};
export type VendorData = {
vendor: string;
rows: VendorContent[];
};
<DataTable defaultData={vendorData} />
Structurally, the design I have looks like this:
Within the DataTable itself, I have something like this:
const columns = [
columnHelper.display({
id: 'actions',
cell: (props) => <p>test</p>,
}),
columnHelper.accessor('name', {
header: () => 'Name',
cell: (info) => info.renderValue(),
}),
columnHelper.accessor('user_name', {
header: () => 'User name',
cell: (info) => info.renderValue(),
}),
columnHelper.accessor('dob', {
header: () => 'DOB',
cell: (info) => info.renderValue(),
}),
];
const DataTable = (props: DataTableProps) => {
const { defaultData } = props;
const [data, setData] = React.useState(() =>
defaultData.flatMap((item) => item.rows)
);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
Now, here's the kicker. Vendor1, and Vendor2 are collapsible rows, and need to be somehow passed into the table, but the defaultData.flatMap((item) => item.rows) which sets up each row, is obviously removing this information / structure. Ergo, I've nothing to hook into to try and render that in the table.
Things I've tried:
const [data, setData] = React.useState(() =>
defaultData
);
Once I try and pass the full Data object in, the column definition complains. (Data passed is no longer an array).
getSubRows within the React Table hook seems to require a full definition of all the columns (all I want is the vendor name there).
Header groups seem to be rendered before the headings, but what I actually want is almost a 'row group' that is expandable / collapsible?
How would I achieve a design similar to the below, with a data structure as illustrated, such that there are row 'headings' which designate the vendor?
I've setup a codesandbox here that sort of illustrates the problem: https://codesandbox.io/s/sad-morning-g5is0e?file=/src/App.js
First steps
Starting from this docs and this example from docs we can create a colapsable row like this (click on the vendor to expand/collapse next rows).
Steps to do it:
useExpanded
and add it as the second argument of useTable
(after the object containing { columns, data }
)defaultData.flatMap((item) => item.rows)
with myData.map((row) => ({ ...row, subRows: row.rows }))
(or if you can just rename rows
to subRows
and you can just send defaultData
(without any mapping or altering of the data).const columns = React.useMemo(() => [
the following snippet:{
id: "vendor",
Header: ({ getToggleAllRowsExpandedProps }) => (
<span {...getToggleAllRowsExpandedProps()}>VENDOR</span> // this can be changed
),
Cell: ({ row }) =>
row.original.vendor ? <span {...row.getToggleRowExpandedProps({})}>row.vendor</span> : null, // render null if the row is not expandable
},
4. Add the DOB to the columns
Formatting the rows
With some reverse engineering from this question (using colspan) we can render only one value per row (reverse because we want the main row to use all 4 cells).
This will also make the first header part very small and lead to something like this for example.
How we got here from First steps
:
vendor
key andMain difference in a snippet:
{
row.original.vendor ? (
<td {...row.cells[0].getCellProps()} colSpan={4}>
{row.cells[0].render("Cell")}
</td>
) : (
row.cells.map((cell) => {
return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
})
);
}
Unfortunately I don't think there is another (easier / more straight forward) way to do it (I mean I don't think this is bad, but I think it can be confusing especially if you try to figure it out searching trough so many pages of docs and there is no guide in this direction as far as I know).
Also please note I tried to highlight and explain the process. There might be some small extra adjustments needed in the code.