镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

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

Closed

Description

@marceloverdijk

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.

Activity

  1. dimbslmh commented on Aug 9, 2022

    @dimbslmh

    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
  2. LautaroRiveiro commented on Aug 12, 2022

    @LautaroRiveiro

    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)```
    
  3. decayedCell commented on Aug 18, 2022

    @decayedCell

    As a temporary fix I added

    enableGlobalFilter: false,
    

    To my number column

  4. marceloverdijk commented on Aug 18, 2022

    @marceloverdijk
    ContributorAuthor

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

  5. decayedCell commented on Aug 18, 2022

    @decayedCell

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

  6. marceloverdijk commented on Aug 18, 2022

    @marceloverdijk
    ContributorAuthor

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

  7. jeg924 commented on Aug 22, 2022

    @jeg924

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

  8. scoussens commented on Sep 1, 2022

    @scoussens

    @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

  9. oldo commented on Sep 13, 2022

    @oldo

    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);
    };
    
  10. alisonatwork commented on Nov 1, 2022

    @alisonatwork

    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.

  11. talatkuyuk commented on Nov 6, 2022

    @talatkuyuk
    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.

  12. OmerWow commented on Nov 14, 2022

    @OmerWow

    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.

  13. codebycarlos commented on Dec 8, 2022

    @codebycarlos

    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());
          }
          ...
        });
    
  14. stupeyca commented on Feb 24, 2023

    @stupeyca

    @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'"
    
  15. ssarahs-lab commented on Mar 2, 2023

    @ssarahs-lab

    works fine when I used "includesString"?

  16. soymartinez commented on Jun 26, 2023

    @soymartinez

    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))
    }
  17. hadnazzar commented on Aug 1, 2023

    @hadnazzar

    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

  18. elijahkimani commented on Oct 14, 2023

    @elijahkimani

    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,
          }
        )
        
    
  19. eikowagenknecht commented on Feb 15, 2024

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

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