icflorescu / mantine-datatable

The table component for your Mantine data-rich applications, supporting asynchronous data loading, column sorting, custom cell data rendering, context menus, nesting, Gmail-style batch row selection, dark theme, and more.
https://icflorescu.github.io/mantine-datatable/
MIT License
949 stars 68 forks source link

Expanding rows behavior #609

Closed lfsaga closed 4 months ago

lfsaga commented 4 months ago

Describe the bug IDK why my Next project turned into this bug, but the expanding rows official example it's expanding/collapsing all rows at same time, I had it working properly before but now it fails like this.

Expected behavior Expand a row at once.

Screenshots http://i.imgur.com/pPLeGvP.mp4

Desktop (please complete the following information):

mikkurogue commented 4 months ago

Do you have an index column to use? I remember having a similar issue at work and it turned out that it would need a unique index per row to handle the row expansion

for context we define the idAccessor prop to be the id accessor. And in the rowExpansion callback we define the content function with the record and its index. IF we did not provide an idAccessor then it would open all rows.

Edit:

This is what I mean for idAccessor:


<DataTable
        records={someRecords}
        // define columns
        columns={someColumns}
        idAccessor={'important_unique_row_id_define_in_columns'} // This is important**
        // execute this callback when a row is clicked
        rowExpansion={{
          allowMultiple: true,
          content: ({ record, recordIndex }: any) => {
            return <SomeComponent key={recordIndex} record={record} />;
          },
        }}
      />
lfsaga commented 4 months ago

Hi @mikkurogue,

I appreciate the recent fix. However, I've encountered a new challenge related to the row expanding logic in the component.

Context: In my current implementation, I'm not sending a unique ID for each element to the front end due to data modeling requirements. Instead, I use a combination of two fields (slug and version) as a composite index to ensure uniqueness.

Problem:

Question: Does this component support the declaration of a composed index? If not, how should I handle this situation where both slug and version together create a unique identifier for each row?

Looking forward to your guidance on this.

Thank you!

mikkurogue commented 4 months ago

hey @lfsaga What you could do, is in your column definition create a hidden column that could be a combination of whatever 2 fields you want.

Something like this:

// Column definition
const cols = [
  {
    accessor: 'unique_row_id',
    hidden: true
  },
  {
    accessor: 'column_1',
    title: 'Column 1',
  }
]

and wherever you create your records array, you then have to create the composition value.

 // data response from api as example
 const dataFromApi = [
    {
      slug: 'route_slug',
      version: 'v1',
      column_1: 'Route slug v1'
    },
    {
      slug: 'route_slug',
      version: 'v2',
      column_1: 'Route slug v2'
    }
  ]

  const records = dataFromApi.map((item) => {
    return {
      unique_row_id: `${item.slug}_${item.version}`, // note the composition here
      column_1: item.column_1
    }
  })

And your datatable implementation would then have at least these 3 props:

<DataTable columns={columns} records={records} idAccessor={'unique_col_id'} />

As long as the combination of ${item.slug}_${item.version} is always unique you should be fine. If this is not the case, I'd then recommend explicitly defining an index column in the same way and populating this from the Array.map(cb(element, index) index and making sure indexes never overlap on the parent.

Edit: I do understand that this does create extra complexity and potential overhead as you have to create a new array to use, but this should be "doable" if your objects arent too big and you potentially fetch records by pagination.

lfsaga commented 4 months ago

Thank you so much @mikkurogue it's now fixed.

I ended up faking an ID field with the array index for each item and setting it as idAccessor for the component.

Results:

import React, { useState, useEffect, useMemo } from 'react';
import { DataTable } from 'mantine-datatable';

export const PaginationDatatable = ({ 
  columns, 
  data, 
  rowExpansion, 
  allowSelection = false, 
  pageSizes = [5, 10, 15] 
}) => {
  const [selectedRecords, setSelectedRecords] = useState([]);
  const [pageSize, setPageSize] = useState(pageSizes[0]);
  const [page, setPage] = useState(1);
  const [records, setRecords] = useState([]);

  useEffect(() => {
    const from = (page - 1) * pageSize;
    const to = Math.min(from + pageSize, data.length);
    setRecords(data.slice(from, to).map((record, index) => ({ ...record, item_id: from + index + 1 })));
  }, [data, page, pageSize]);

  const columnsWithItemId = useMemo(() => [{ accessor: 'item_id', hidden: true }, ...columns], [columns]);

  const dataTableProps = {
    columns: columnsWithItemId,
    records: records,
    totalRecords: data.length,
    recordsPerPage: pageSize,
    page: page,
    onPageChange: (newPage) => setPage(newPage),
    recordsPerPageOptions: pageSizes,
    onRecordsPerPageChange: (newPageSize) => {
      setPageSize(newPageSize);
      setPage(1);
    },
    idAccessor: 'item_id',
    rowExpansion: rowExpansion,
    paginationSize: 'xs'
  };

  if (allowSelection) {
    dataTableProps.selectedRecords = selectedRecords;
    dataTableProps.onSelectedRecordsChange = setSelectedRecords;
  }

  return (
    <DataTable {...dataTableProps} />
  );
};
mikkurogue commented 4 months ago

Nice, cool stuff! Glad to be of service