table
table copied to clipboard
[v8] Global filter gives `TypeError: l.toLowerCase is not a function` when using number fields
Describe the bug
With latest 8.5.11
version using the Global filter in combination with number
fields gives:

Note this was already raised in https://github.com/TanStack/table/issues/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
- [X] I agree to follow this project's Code of Conduct
- [X] 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.
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
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)```
As a temporary fix I added
enableGlobalFilter: false,
To my number column
@decayedCell unfortunately that is not really a fix when having a global filter in the first place 😉
@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.
Exactly what I meant, the column is completely ignored and that's not what I want
@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.
@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
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);
};
I think the reason why this is happening is because of the following commit: a5578ac5670b6c8739ce9e12f8045a17dbcd8823
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:
- Change all your columns to string type
- Use your own filter function which allows either string or number type
- 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.
- 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) - There should be a type-flexible version of
includesString
which can at least handle all of the types allowed bygetColumnCanGlobalFilter
, or even better handle any type thrown at it by stringifying it - 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.
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.
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.
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());
}
...
});
@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'"
works fine when I used "includesString"?
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))
}
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
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
- format the date and amount values to string as the return value of the
accessorFn
- 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,
}
)
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);
},