table icon indicating copy to clipboard operation
table copied to clipboard

Row selection with filter from leaf rows not behaving as expected

Open alexanderluiscampino opened this issue 2 years ago • 4 comments

Describe the bug

Kinda weird why this is happening, and maybe I am missing something in the documentation. But I am trying to set up a table with sub-rows, filtering, sorting, and row selection. I need the leaf rows to be filtered hence I used the option filterfromleafrows.

Something happens when this option is enabled that the checkbox to select all rows becomes unavailable. Upon further digging I found that the issue is with the methods: getIsAllRowsSelected and getIsSomeRowsSelected that return false always no matter what the row selection is. The checkbox itself behaves as expected, but because the inputs for indeterminate and checked, as given in the example here, are always false, the user cannot use the header checkbox to select all rows.

I need this functionality since my users will basically apply some sort of filtering and then they wish to select all.

Below I have a minimal reproduction example, where I add a checkbox input to switch the filterfromleafrows option. Do notice how the header checkbox, the select all one, if selected prior, will become unchecked as we flip the filterfromleafrows control.

If not filter is applied to the table, it behaves as it should. Hence, this only happens post-filtering the table.

I also added a filter input to the example.

Your minimal, reproducible example

Couldn't get your checkbox to work on the codesandbox.

Steps to reproduce

import React, { Fragment } from 'react';

import type { FilterFn, Row } from '@tanstack/react-table';
import { rankItem } from '@tanstack/match-sorter-utils';

import {
    flexRender,
    getCoreRowModel,
    getFilteredRowModel,
    getPaginationRowModel,
    useReactTable,
    createColumnHelper,
} from '@tanstack/react-table';

import { Checkbox, Input, LabeledControl, Label } from '@chakra';

interface Entry {
    id: string;
    name: string;
    description: string;
}

export const fuzzyFilter: FilterFn<Entry> = (
    row: Row<Entry>,
    columnId: string,
    value: string,
    addMeta
) => {
    console.log({ row, columnId, value });
    // Rank the item
    const itemRank = rankItem(row.getValue(columnId), value);

    // Store the itemRank info
    addMeta({
        itemRank,
    });

    // Return if the item should be filtered in/out
    return itemRank.passed;
};

export default function About() {
    const [rowSelection, setRowSelection] = React.useState({});
    const [globalFilter, setGlobalFilter] = React.useState('');
    const [filterFromLeafRows, setFilterFromLeafRows] = React.useState(true);

    const data = [
        { id: '1', name: 'John', description: 'Smith' },
        { id: '2', name: 'Jane', description: 'Doe' },
        { id: '3', name: 'John', description: 'Doe' },
        { id: '4', name: 'Jane', description: 'Smith' },
    ] as Entry[];

    const factory = createColumnHelper<Entry>();

    const columnCheckbox = factory.display({
        id: 'checkbox',
        cell: ({ row }) => (
            <Checkbox
                checked={row.getIsSelected()}
                indeterminate={row.getIsSomeSelected()}
                onChange={row.getToggleSelectedHandler()}
            />
        ),
        header: ({ table }) => (
            <Checkbox
                checked={table.getIsAllRowsSelected()}
                indeterminate={table.getIsSomeRowsSelected()}
                onChange={table.getToggleAllRowsSelectedHandler()}
            />
        ),
        enableHiding: true,
    });

    const columnId = factory.accessor('id', {
        id: 'id',
        cell: ({ getValue }) => getValue(),
        header: 'ID',
    });

    const columnName = factory.accessor('name', {
        id: 'name',
        cell: ({ getValue }) => getValue(),
        header: 'Name',
    });

    const columnDescription = factory.accessor('description', {
        id: 'description',
        cell: ({ getValue }) => getValue(),
        header: 'Description',
    });

    const table = useReactTable({
        data,
        columns: [columnCheckbox, columnId, columnName, columnDescription],
        state: {
            rowSelection,
            globalFilter,
        },
        filterFns: {
            fuzzy: fuzzyFilter,
        },
        enableRowSelection: true,
        onRowSelectionChange: setRowSelection,
        getCoreRowModel: getCoreRowModel(),
        getFilteredRowModel: getFilteredRowModel(),
        getPaginationRowModel: getPaginationRowModel(),
        debugTable: true,
        filterFromLeafRows,

        onGlobalFilterChange: setGlobalFilter,
        globalFilterFn: fuzzyFilter,
    });

    return (
        <Fragment>
            <div>
                <h1>About - {globalFilter}</h1>
            </div>
            <div>
                <Input
                    onChange={(e) => setGlobalFilter((e.currentTarget as HTMLInputElement).value)}
                />
                <LabeledControl
                    label={<Label>Filter From Leaf Rows</Label>}
                    control={
                        <Checkbox
                            checked={filterFromLeafRows}
                            onChange={(e) =>
                                setFilterFromLeafRows((e.currentTarget as HTMLInputElement).checked)
                            }
                        />
                    }
                />
            </div>
            <table>
                <thead>
                    {table.getHeaderGroups().map((headerGroup) => (
                        <tr key={headerGroup.id}>
                            {headerGroup.headers.map((header) => (
                                <th key={header.id} colSpan={header.colSpan}>
                                    {header.isPlaceholder ? null : (
                                        <React.Fragment>
                                            {flexRender(
                                                header.column.columnDef.header,
                                                header.getContext()
                                            )}
                                        </React.Fragment>
                                    )}
                                </th>
                            ))}
                        </tr>
                    ))}
                </thead>
                <tbody>
                    {table.getRowModel().rows.map((row) => (
                        <tr key={row.id}>
                            {row.getVisibleCells().map((cell) => (
                                <td key={cell.id}>
                                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                                </td>
                            ))}
                        </tr>
                    ))}
                </tbody>
            </table>
            <div>Filtering from leaf rows: {filterFromLeafRows ? 'Yes' : 'No'}</div>
            <div>Row selection: {JSON.stringify(rowSelection)}</div>
            <div>getIsAllRowsSelected: {String(table.getIsAllRowsSelected())}</div>
            <div>getIsSomeRowsSelected: {String(table.getIsSomeRowsSelected())}</div>
        </Fragment>
    );
}

Expected behavior

To be able to select all rows upon filtering the table.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

Linux on Chrome

react-table version

8.7.9

TypeScript version

4.5.4

Additional context

No response

Terms & Code of Conduct

  • [X] I agree to follow this project's Code of Conduct
  • [X] I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

alexanderluiscampino avatar Feb 24 '23 06:02 alexanderluiscampino

Just to add a bit more from researching, it looks like the problem is somewhere in the method getFilteredRowModel which when a global filter is active in conjunction with filterFromLeafRows doesn't behave as expected.

alexanderluiscampino avatar Feb 24 '23 22:02 alexanderluiscampino

This might be connected to https://github.com/TanStack/table/issues/4768. Please check the additional context of this issue, as it points out https://github.com/TanStack/table/blob/main/packages/table-core/src/utils/filterRowsUtils.ts#L63 as the possible origin of this bug.

This could be relevant for this issue, too.

jochenschmich-aeberle avatar Mar 20 '23 11:03 jochenschmich-aeberle

@alexanderluiscampino Did you find where the problem is and/or some sort of workaround?

It's not ideal but for now I'm reapplying the filter where I need it like this: matchSorter(subRows, globalFilter)

Zertz avatar May 28 '24 19:05 Zertz

@Zertz I haven't gotten to the bottom of it...

alexanderluiscampino avatar Jun 10 '24 01:06 alexanderluiscampino