Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v8] Global filter gives TypeError: l.toLowerCase is not a function when using number fields #4280

Open
2 tasks done
marceloverdijk opened this issue Aug 5, 2022 · 19 comments

Comments

@marceloverdijk
Copy link
Contributor

Describe the bug

With latest 8.5.11 version using the Global filter in combination with number fields gives:

image

Note this was already raised in #4210 but still happens with latest version unfortunately.

I think this was introduced somewhere in 8.3.x line as I first encountered this while upgrading from 8.2.x to 8.3.3.

A workaround like

      {
        id: 'matches',
        header: 'Matches',
        accessorFn: (originalRow) => originalRow.matches.toString(), // matches is a number
        // accessorKey: 'matches', // this worked before
      },

seems to work for the filtering, but this would also impact the sorting I would assume. Sorting on a string value instead of the number value will have impact (1, 2, 10 vs 1, 10, 2).

Your minimal, reproducible example

.

Steps to reproduce

See above.

Expected behavior

Global filter to work with number fields out of the box.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

macOS, chrome

react-table version

v8.5.11

TypeScript version

4.7.4

Additional context

If reproducible example is needed let me know.

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.
@dimbslmh
Copy link

dimbslmh commented Aug 9, 2022

Experiencing the same issue in v8.5.11.

The following is shown in Console:

The above error occurred in the component
... stack trace
React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.
declare module "@tanstack/table-core" {
  interface FilterFns {
    testFilter: FilterFn<unknown>;
  }
}

function testFalsey(val: any) {
  return val === undefined || val === null || val === "";
}
  const [globalFilter, setGlobalFilter] = useState("");

  const testFilter: FilterFn<any> = (
    row,
    columnId: string,
    filterValue: unknown,
  ) => {
    console.log(columnId, filterValue);
    return true;
  };

  testFilter.resolveFilterValue = (val: any) => {
    console.log("resolveFilterValue", val);
    return `${val}`;
  };

  testFilter.autoRemove = (val: any) => {
    console.log("resolveFilterValue", val);
    return testFalsey(`${val}`);
  };

  const table = useReactTable({
    data,
    columns,
    state: {
      globalFilter,
    },
    filterFns: {
      testFilter,
    },
    onGlobalFilterChange: setGlobalFilter,
    // globalFilterFn: testFilter,           <-- custom filter is only triggered as globalFilterFn
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
  });

Things I've tried:

  • All built-in filter functions. -> results in above error
  • Add custom filter to filterFns, and set above custom filterFn. -> results in above error (filterFn not even triggered)
  • Set custom filterFn as globalFilterFn. -> triggered and not crashing
  • Set enableGlobalFilter: false -> obviously no error

@LautaroRiveiro
Copy link

LautaroRiveiro commented Aug 12, 2022

I am so novice on this. I copied the includesString built-in function and converted the value to string. And now use that as the globalFilterFn.
Do you think it could be useful?


function testFalsey (val: any): boolean {
  return val === undefined || val === null || val === ''
}

export const includesString: FilterFn<any> = (
  row,
  columnId: string,
  filterValue: string
) => {
  const search = filterValue.toLowerCase()
  // Convert to String
  const value = String(row.getValue<string>(columnId))
  return value?.toLowerCase().includes(search)
}

includesString.autoRemove = (val: any) => testFalsey(val)```

@decayedCell
Copy link

As a temporary fix I added

enableGlobalFilter: false,

To my number column

@marceloverdijk
Copy link
Contributor Author

@decayedCell unfortunately that is not really a fix when having a global filter in the first place 😉

@decayedCell
Copy link

decayedCell commented Aug 18, 2022

@decayedCell unfortunately that is not really a fix when having a global filter in the first place 😉

Not sure what you mean by this. I have ran into the same issue, I have 2 tables and both use global filter and a search component that sets the global filter. For the table that only has strings as fields there are no issues, but on the other one I ran into the same issue mentioned by you. So I disabled GlobalFilter for only that column that uses numbers and now I have no issues using my search component. Obviously the negative is that that number column is completely ignored now, but until a fix it makes the table not crash at least while still providing the global filter functionality for the other columns.

@marceloverdijk
Copy link
Contributor Author

Exactly what I meant, the column is completely ignored and that's not what I want

@jeg924
Copy link

jeg924 commented Aug 22, 2022

@marceloverdijk You can keep the data types intact and just pass in sanitized tableData before you initialize the table like this:

  useEffect(() => {
    setTableData(
      data.map((row: any) => {
        Object.keys(row).forEach((column: any) => {
          if (row[column]) {
            row[column] = row[column].toString();
          } else {
            row[column] = '';
          }
        });
        return row;
      })
    );
  }, [data]);

It's not as pretty as just passing in data to useReactTable but it will solve your problems.

@scoussens
Copy link

@marceloverdijk while this doesn't solve whatever the underlying issue in the code is... you can follow one of the examples to implement a fuzzy filter that replaces the global and seems to work fine.

https://codesandbox.io/s/github/tanstack/table/tree/main/examples/react/filters?from-embed=&file=/src/main.tsx

@oldo
Copy link

oldo commented Sep 13, 2022

I took a page from @LautaroRiveiro suggestion by overriding the globalFilterFn, but instead of casting all values as a String I do a check on whether the field is a number, and if so only then make a conversion to a string:

const globalFilterFn: FilterFn<T> = (row, columnId, filterValue: string) => {
  const search = filterValue.toLowerCase();

  let value = row.getValue(columnId) as string;
  if (typeof value === 'number') value = String(value);

  return value?.toLowerCase().includes(search);
};

@alisonatwork
Copy link

I think the reason why this is happening is because of the following commit: a5578ac

The default behavior of getColumnCanGlobalFilter will now return true for any columns that are either string or number, but the default filter functions don't have the same flexibility. So the various options for working around the bug are as follows:

  1. Change all your columns to string type
  2. Use your own filter function which allows either string or number type
  3. Override getColumnCanGlobalFilter in your table options so that it goes back to old behavior of only returning true for string type columns

I also hit this problem from a different direction - I wanted to filter on string array type columns - which is possible to do if you have a flexible filter function that handles array types - but that column would never appear because getColumnCanGlobalFilter filtered it out.

I think there are three things that could happen going forward, assuming the goal is that global filters should be type-flexible in the default case.

  1. Provide a convenient factory function that can create a getColumnCanGlobalFilter function based on supported types (i.e. we should be able to specify which types we want to allow for a particular table without having to override the entire function supplied in default filter options)
  2. There should be a type-flexible version of includesString which can at least handle all of the types allowed by getColumnCanGlobalFilter, or even better handle any type thrown at it by stringifying it
  3. The documentation says that if getColumnCanGlobalFilter is not supplied, all columns are assumed to be globally filterable (https://tanstack.com/table/v8/docs/api/features/filters#can-filter). But that doesn't appear to be the case, because the default options do filter out certain columns (https://github.com/TanStack/table/blob/main/packages/table-core/src/features/Filters.ts#L174). So perhaps the documentation should be updated too.

I also noticed if you don't supply a globalFilterFn it defaults to "auto", which I guess is supposed to figure out the correct filter to use depending on the column type and avoid the user having to do any of this complicated setup themselves, but in my case that's not working either, because it decides to use includesString for a number column.

@talatkuyuk
Copy link
Contributor

The same issue. I use the latest version "@tanstack/react-table": "^8.5.22";

I am getting TypeError: o.toLowerCase is not a function when I use the globalFilter feature which is basic one and is not fuzzy implementation.

There are one number-type data column, two string-type data columns and a number of display columns in my Table definition. (There is no group header)

If I remove the sole number-type data column, the table does not render at all having the same error.
If I keep the number-type data column, I receive the error when I type the input field which is debounced input related with globalFilter as it is in the examples in the tanstack web site.

@OmerWow
Copy link

OmerWow commented Nov 14, 2022

I get the same "o.toLowerCase is not a function" error when I have a number-type column, even on latest v8.5.27 released today.

I solved it by simply adding .toString() on the number column's accessorFn.

While the solution is quite simple, it was not easy to understand and I hope it'll be fixed soon, or at least have better error handling so developers will have a better idea of what to do in this situation.

@codebycarlos
Copy link

Experiencing the same issue. Solved it thanks to @oldo's suggestion.

const table = useReactTable({
      data,
      columns,
      globalFilterFn: (row, columnId, filterValue) => {
        const safeValue = (() => {
          const value = row.getValue(columnId);
          return typeof value === 'number' ? String(value) : value;
        })();

        return safeValue?.toLowerCase().includes(filterValue.toLowerCase());
      }
      ...
    });

@stupeyca
Copy link

stupeyca commented Feb 24, 2023

@oldo's solution did the trick for me. But, I was just wondering if there's an actual difference when validating the type of value this way:

if (!isNaN(parseInt(value))) value = String(value);

The thing is that the conditional in the solution gives:

"Invalid 'typeof' check: 'value' cannot have type 'number'"

@ssarahs-lab
Copy link

works fine when I used "includesString"?

@soymartinez
Copy link

When you define your columns and you are going to filter specific data from a column make sure to add the filterFn property:

filterFn: (row, id, value) => {
    return value.includes(row.getValue(id))
}

@hadnazzar
Copy link

When you define your columns and you are going to filter specific data from a column make sure to add the filterFn property:

filterFn: (row, id, value) => {
    return value.includes(row.getValue(id))
}

Thats worked for me! Thanks

@elijahkimani
Copy link

elijahkimani commented Oct 14, 2023

Alternative way to think about it

I was working with date and numeric columns
The accessorFn used would return number or dates or undefined depending on the column. This would then make the native sorting functionality to work

The column's cell function would then format the numbers or dates for rendering

I noticed that the globalFilterFn was ignoring these specific fields when I debugged my code
To solve this, I had to

  1. format the date and amount values to string as the return value of the accessorFn
  2. Create custom sortingFn for the respective columns and access the raw value from Row data

Below is an example of one column definition

const columnHelper = createColumnHelper<Order>();

const DateSortFn =
  (accessor: (row: Order) => Date | undefined) =>
  (rowA: Row<Order>, rowB: Row<Order>, columnId: any): number => {
    const rowAValue = accessor(rowA.original) || new Date(0);
    const rowBValue = accessor(rowB.original) || new Date(0);

    return rowAValue < rowBValue ? 1 : -1;
  };

const validUntilColumnDef = columnHelper.accessor(
      (row) => formatDate(row.quoteValidTo, "MMM d, yyyy"),
      {
        header: getColumnHeaderName("valid-until"),
        sortingFn: DateSortFn((row) => row.quoteValidTo),
        id: "valid-until",
        cell: HighlightableCell,
      }
    )
    

@eikowagenknecht
Copy link

filterFn: (row, id, value) => {
    return value.includes(row.getValue(id))
}

Shouldn't this be the other way round? At least for me, to filter a number column the following works:

filterFn: (row, id, value: string) => {
  const rowValue = row.getValue<string>(id).toString();
  return rowValue.includes(value);
},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests