TanStack / table

🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table
https://tanstack.com/table
MIT License
25.02k stars 3.07k forks source link

Row selection with filter from leaf rows not behaving as expected #4724

Open alexanderluiscampino opened 1 year ago

alexanderluiscampino commented 1 year ago

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

alexanderluiscampino commented 1 year ago

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.

jochenschmich-aeberle commented 1 year ago

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.

Zertz commented 4 months ago

@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)

alexanderluiscampino commented 3 months ago

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