Skip to content

row selection not working for nested rows (getSubRows) #4878

Closed

Description

@csantos-nydig

Describe the bug

row-selection in the group rows are not correctly reacting to their sub-rows row-selection state

Your minimal, reproducible example

https://codesandbox.io/s/row-selection-sub-rows-kfgh2j

Steps to reproduce

  1. Go to https://codesandbox.io/s/row-selection-sub-rows-kfgh2j
  2. Expand the first group row
  3. Select the first sub-row
  4. ✅ Notice the parent row checkbox being "indeterminate"
    image
  5. Select the other 2 sub-rows in that same group
  6. 🛑 Notice the parent row checkbox is "unchecked", when it should be "checked"
    image

Probably the same issue:

  1. Go to the same codesandbox: https://codesandbox.io/s/row-selection-sub-rows-kfgh2j
  2. Expand the first group row
  3. Select the checkbox from the column header
  4. ✅ Notice all rows (parent and children) are selected
    image
  5. Unselect the first sub-row of the expanded group
  6. 🛑 Notice the parent row checkbox is stuck on "checked", when it should be "indeterminate"
    image

Expected behavior

As a user, I expected the parent row to react to the row-selection status of its children, regardless on how they are selected/unselected:

  • some sub-rows selected: parent row "indeterminate"
  • no sub-rows selected: parent row "unchecked"
  • all sub-rows selected: parent row "checked"

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • Browser: N/A
  • OS: N/A

Happens in codesandbox

react-table version

v8.9.1

TypeScript version

N/A - whatever codesandbox uses

Additional context

No response

Terms & Code of Conduct

  • I agree to follow this project's Code of Conduct
  • 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.

Activity

  1. VincEnterprise commented on May 30, 2023

    @VincEnterprise

    Also see: #4720

    @tannerlinsley could you please take a look at this 🥹🙏

    I really want to use tabstack table for a project but this specific issue is preventing me from being able to use it.

    This naive UI example here (cascade checking) has the behavior we are expecting:

    https://www.naiveui.com/en-US/os-theme/components/tree#cascade.vue

  2. VincEnterprise commented on Jun 7, 2023

    @VincEnterprise

    bump

  3. artem-zaiko-yara commented on Jun 14, 2023

    @artem-zaiko-yara

    I use this workaround until the bug is fixed (React):

    useEffect(() => {
      const { rows } = table.getRowModel();
      rows.forEach((row) => {
        const allSuRowsSelected = row.subRows.length > 0 && row.subRows.every(({ getIsSelected }) => getIsSelected());
        if (allSuRowsSelected) {
          row.toggleSelected(true);
        }
      });
    }, [rowSelection, table]);
  4. R-Bower commented on Jan 27, 2024

    @R-Bower

    Link to fix:
    #3965 (comment)

  5. Profesor08 commented on Jan 30, 2024

    @Profesor08

    bump, issue is actual

    Current workaround is to toggle manually

      useEffect(() => {
        const { rows } = table.getRowModel();
    
        rows.forEach((row) => {
          if (row.subRows.length > 0) {
            if (row.getIsAllSubRowsSelected() === true) {
              row.toggleSelected(true);
            } else if (row.getIsSomeSelected() === false) {
              row.toggleSelected(false);
            }
          }
        });
      }, [rowSelection, table]);
  6. VACincoming commented on Feb 10, 2024

    @VACincoming

    Any updates on this issue?
    I'm planning to update from v7 -> v8 because v7 has a bug with isSomeSelected prop for nested rows, but looks like updating does not make any sense because a v8 still has a problem with checkboxes for nested rows...

    #3965

  7. pelletier197 commented on Feb 28, 2024

    @pelletier197

    Just noticed the same issue 👍

  8. Macs89 commented on Mar 20, 2024

    @Macs89

    Yep, I've run into this issue too.

  9. minhdtb-tanaakk commented on Mar 27, 2024

    @minhdtb-tanaakk

    same issue here

  10. cthomesmatt commented on Apr 3, 2024

    @cthomesmatt

    bump, issue is actual

    Current workaround is to toggle manually

      useEffect(() => {
        const { rows } = table.getRowModel();
    
        rows.forEach((row) => {
          if (row.subRows.length > 0) {
            if (row.getIsAllSubRowsSelected() === true) {
              row.toggleSelected(true);
            } else if (row.getIsSomeSelected() === false) {
              row.toggleSelected(false);
            }
          }
        });
      }, [rowSelection, table]);

    This is a great fix since the issue is still occurring, thank you! I noticed that the table.getIsSomeRowsSelected() handler isn't counting grouped rows that are being selected, so to fix this I fixed up a function you can use to check if any are selected in the grouped or subrows, hope it helps someone like it did for me.

    function getIsAnyRowsSelected(table: Table<any>) {
        const rows = table.getExpandedRowModel().rows;
        const { rowSelection } = table.getState();
    
        let isAnyRowsSelected = rows.some(row => {
          if (rowSelection[row.id]) return true;
    
          if (row.subRows.some(subRow => rowSelection[subRow.id])) return true;
    
          return false;
        });
    
        return isAnyRowsSelected;
      }
    
  11. vladcos commented on Apr 17, 2024

    @vladcos

    same issue

  12. mttigg commented on May 8, 2024

    @mttigg

    Following this issue.

  13. xuyunan commented on May 30, 2024

    @xuyunan

    same issue

  14. etiennecoyacabelio commented on Jul 18, 2024

    @etiennecoyacabelio

    same issue

  15. thescripted commented on Oct 11, 2024

    @thescripted

    I'm curious what makes this a bug?

    With the above proposal, selecting all of the children and therefore making the parent row selected, then the parent row can never be a selectable element. There is no independence between parent and children. The opposite would be true too (deselecting all of the children will automatically deselect its parent).

  16. TsengTofu commented on Jan 21, 2025

    @TsengTofu

    follow this.

  17. ahmedprusevic commented on Jun 8, 2025

    @ahmedprusevic

    +1

  18. Rushour0 commented on Sep 8, 2025

    @Rushour0

    bump here

  19. Radomir-Drukh commented on Oct 14, 2025

    @Radomir-Drukh

    Update: contains bugs, do not use

    Provided solution sometimes leaves parent row selected in the state when not all its children have been selected. This leads to the parent row checkbox being unresponsive

    Here is an improved solution as a custom hook:

    Old code
    import * as React from 'react';
    
    import type {RowSelectionState, Table} from '@tanstack/react-table';
    
    export const useRowSelectionWithSubRows = (
        table: Table<any>,
        rowSelection: RowSelectionState,
        setRowSelection: React.Dispatch<React.SetStateAction<RowSelectionState>>,
    ) => {
        React.useEffect(() => {
            const {rows} = table.getRowModel();
    
            const rowSelectionUpdated = rowSelection;
    
            rows.forEach((row) => {
                let isSelected = false;
    
                if (row.subRows.length > 0) {
                    isSelected = row.getIsAllSubRowsSelected() === true;
                } else {
                    isSelected = row.getIsSelected();
                }
    
                if (isSelected) {
                    rowSelectionUpdated[row.id] = true;
                } else if (row.id in rowSelectionUpdated) {
                    delete rowSelectionUpdated[row.id];
                }
            });
    
            setRowSelection(rowSelectionUpdated);
        }, [rowSelection, table]);
    };
  20. Radomir-Drukh commented on Nov 10, 2025

    @Radomir-Drukh

    Here is revised solution

    import * as React from 'react';
    
    import type {RowSelectionState, Updater} from '@tanstack/react-table';
    
    interface RowSelectionProps<T> {
        rowSelection: RowSelectionState;
        setRowSelection: React.Dispatch<React.SetStateAction<RowSelectionState>>;
        tableData: T[];
        item: T;
        getSubRows: (item: T) => T[] | undefined;
        getRowId: (row: T) => string;
    }
    
    const processItemSelection = <T>({
        rowSelection,
        item,
        getSubRows,
        getRowId,
    }: Omit<RowSelectionProps<T>, 'setRowSelection' | 'tableData'>): boolean => {
        let isSelected = true;
    
        const itemId = getRowId(item);
    
        const subRows = getSubRows(item) ?? [];
    
        if (subRows.length > 0) {
            subRows.forEach((subRow) => {
                const isSubRowSelected = processItemSelection({
                    item: subRow,
                    rowSelection,
                    getSubRows,
                    getRowId,
                });
    
                if (!isSubRowSelected) {
                    isSelected = false;
                }
            });
        } else {
            isSelected = rowSelection[itemId] === true;
        }
    
        if (isSelected) {
            rowSelection[itemId] = true;
        } else if (itemId in rowSelection) {
            delete rowSelection[itemId];
        }
    
        return isSelected;
    };
    
    export const useRowSelectionFixedHandler = <T>({
        rowSelection,
        setRowSelection,
        tableData,
        getSubRows,
        getRowId,
    }: Omit<RowSelectionProps<T>, 'item'>) => {
        const fixedRowSelectionHandler = React.useCallback(
            (rowSelectionUpdater: Updater<RowSelectionState>) => {
                const rowSelectionUpdated =
                    typeof rowSelectionUpdater === 'function'
                        ? rowSelectionUpdater(rowSelection)
                        : rowSelectionUpdater;
    
                tableData.forEach((item) => {
                    processItemSelection({
                        item,
                        rowSelection: rowSelectionUpdated,
                        getSubRows,
                        getRowId,
                    });
                });
    
                setRowSelection(rowSelectionUpdated);
            },
            [rowSelection, setRowSelection, tableData, getSubRows, getRowId],
        );
    
        return fixedRowSelectionHandler;
    };
    
  21. added a commit that references this issue on Aug 2, 2026
    0c57cc8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions