React-table: When we use `useExpanded` and `useFilters` subRows disappear when we perform a search

Created on 27 Mar 2020  路  10Comments  路  Source: tannerlinsley/react-table

Describe the bug (required)
When using useExpanded and useFilters subRows disappear when we perform a search.

Provide an example via Codesandbox! (required)
https://codesandbox.io/s/expander-filtering-gjer7

Steps To Reproduce (required)
This happens with all filters, bellow an example:

Search for the exact content of a row from "First Name":

Issue 1: The search doesn't looks for values on subRows
Issue 2: When you clear the search the subRows disappear

Most helpful comment

Well , when you explicitly define the subrows , at least the subrows are not getting removed.
But my problem now is, global filter is not searching the subrows.

useTable(
    {
      columns,
      data,
      initialState: {
        hiddenColumns: hiddenColumns,
      },
      getSubRows: (row: any) => row.subRows,
    },

All 10 comments

I was just about to open an issue for this same behavior as well.
After looking through the code, I believe this behavior was introduced by this change. Before that change, each row was cloned before overwriting the subRows property. But now, each row is no longer cloned; which means that updating the subRows property actually persists a change the the memoized row data.

The workaround is to clone each row yourself inside your filter method(s). But I think this is definitely a bug. IMO, filtering should not be allowed to mutate the subRows like that.

Yeah, that change was necessary for some other things to work that were restricted by doing immutable ops on the sub rows. Welcome to suggestions, but you'll see many tests fail if you revert that change. Any ideas moving forward?

Out-of-the-box behavior seems broken here. I'm using the following to work around this bug.

function globalFilter<Datum extends DatumType>(
  rows: Row<Datum>[],
  columnIds: Extract<keyof Datum, string>[],
  searchQuery: string
) {
  if (!searchQuery) return rows;

  const lowercaseQuery = searchQuery.toLowerCase();
  const rowMatches = (row: Row<Datum>): boolean =>
    Object.values(row.values).some(
      rowValue =>
        typeof rowValue === "string" &&
        rowValue.toLowerCase().includes(lowercaseQuery)
    ) || row.subRows.some(rowMatches);

  return rows.filter(rowMatches);
}


useTable<Datum>(
    {
      ...
      globalFilter,
    },
    useGlobalFilter,
    useFilters,
    useSortBy,
    usePagination,
    useRowSelect,
  );

This also works quite well with Fuzzy searching:

import fuzzy from 'fuzzy';

function globalFilter<Datum extends DatumType>(
  rows: Row<Datum>[],
  columnIds: Extract<keyof Datum, string>[],
  searchQuery: string
) {
  if (!searchQuery) return rows;

  const rowMatches = (row: Row<Datum>): boolean =>
    Object.values(row.values).some(rowValue => {
      return typeof rowValue === "string" && fuzzy.test(searchQuery, rowValue);
    }) || row.subRows.some(rowMatches);

  return rows.filter(rowMatches).map(row => ({ ...row, subRows: row.subRows }));
}

@tannerlinsley You mention the changes in 6d4a733a88dd9c1b9d1e49e5dbc54edd6f2acad5 allowed a few other things to work. Could you share details? I'd like to ensure my cloning workaround isn't going to cause unreliable behavior/regressions. Thank you for all your hard work on this library and congrats on shipping v7.

For my use case I had to write my own useGlobalFilter plugin-hook anyway, so I decided to try to tackle this issue from a different angle.

Here's what I did, at a high level:

  • added a property to each row object called preGlobalFilteredSubRows

    • this is set in the useInstance hook whenever rows or columns change (basically whenever accessRowsForColumn is called in useTable)

  • row.subRows is reset to the value of row.preGlobalFilteredSubRows whenever globalFilterValue is undefined (basically whenever the filter is cleared)
  • Instead of:
row.subRows =
  row.subRows && row.subRows.length
    ? filterRows(row.subRows)
    : row.subRows

I do:

row.subRows =
  row.preGlobalFilteredSubRows && row.preGlobalFilteredSubRows.length
    ? filterRows(row.preGlobalFilteredSubRows)
    : row.subRows

In my limited testing thus far, this has worked to ensure that sub-rows do not disappear whenever the globalFilterValue changes or is removed. Perhaps a similar solution could be applied to useGlobalFilter in react-table?

@tannerlinsley You mention the changes in 6d4a733 allowed a few other things to work. Could you share details?

I would definitely be interested to learn about those issues as well!

Perhaps @aaryn101's solution could be generalized to solve the problem with both global and row filtering, by adding a preFilteredSubRows array to every row?

@aaryn101 Do you have a sample code for reference? I am new to this and need this feature :(

Well , when you explicitly define the subrows , at least the subrows are not getting removed.
But my problem now is, global filter is not searching the subrows.

useTable(
    {
      columns,
      data,
      initialState: {
        hiddenColumns: hiddenColumns,
      },
      getSubRows: (row: any) => row.subRows,
    },

In the next version of RT, this is going to be configurable. You'll be able to choose top-down or bottom-up filtering. For now, v7 is top-down.

@govi74 what are in hiddenColumns?

@govi74 what are in hiddenColumns?

an array of column names

Was this page helpful?
0 / 5 - 0 ratings

Related issues

esetnik picture esetnik  路  3Comments

panfiva picture panfiva  路  3Comments

monarajhans picture monarajhans  路  3Comments

krishna-shenll picture krishna-shenll  路  3Comments

DaveyEdwards picture DaveyEdwards  路  3Comments