TanStack / table

πŸ€– Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table
https://tanstack.com/table
MIT License
24.9k stars 3.07k forks source link

TS Updater "not callable" #4364

Closed Patrick-Ullrich closed 2 years ago

Patrick-Ullrich commented 2 years ago

Describe the bug

onRowSelectionChange, onSortingChange, onExpandedChange, etc are all expecting a OnChangeFn<T>:

declare type Updater<T> = T | ((old: T) => T);
declare type OnChangeFn<T> = (updaterOrValue: Updater<T>) => void;

Usage works fine when passing in a setState method, but I am struggling to enhance it because typescript believes it's a Value and not a function.

e.g.

// βœ… TS is happy with setSorting 
const [sorting, setSorting] = useState<SortingState>([]);
// βœ… TS is happy with handleMultiColumnSortconst 
handleMultiColumnSort = (state: Updater<SortingState>) => {
    // ❌ When logging state we see it is a function
    // when trying to call it though, typescript throws an error:
    console.log('state', state?.());
};

Typescript error:

This expression is not callable.
  Not all constituents of type 'Updater<SortingState>' are callable.
    Type 'SortingState' has no call signatures.ts(2349)

Your minimal, reproducible example

https://codesandbox.io/s/xenodochial-herschel-zlq6y9?file=/src/main.tsx

Steps to reproduce

  1. Go to sandbox and open the console in codesandbox
  2. Click on any header, notice the function and actual object logs
  3. Go to ln 91+ to see the issue

Expected behavior

If state is a function, typescript should show the correct type

How often does this bug happen?

No response

Screenshots or Videos

No response

Platform

OS: macOS

react-table version

8.5.31

TypeScript version

v4.6.3

Additional context

No response

Terms & Code of Conduct

McMerph commented 2 years ago

The parameter passed to onRowSelectionChange is either a value or a function. So, I believe that is why setState works as expected.

One possible solution to this problem:

import {
  ...
  RowSelectionState,
  useReactTable,
} from "@tanstack/react-table";

// get table row selection and setter from store or any other source
const rowSelection: RowSelectionState = ...
const myCustomRowSelectionSetter: (rowSelection: RowSelectionState) => void = ... 

...
const table = useReactTable({
  ...
  onRowSelectionChange: (updaterOrValue) => {
    if (typeof updaterOrValue === "function") {
      myCustomRowSelectionSetter(updaterOrValue(rowSelection));
    } else {
      myCustomRowSelectionSetter(updaterOrValue);
    }
  },
  ...
Patrick-Ullrich commented 2 years ago

Good call on the typeof... I thought about that too, but was confused on this message:

(parameter) state: (old: SortingState) => SortingState
Expected 1 arguments, but got 0.ts(2554)
index.d.ts(632, 33): An argument for 'old' was not provided.

That's where I got hung up. Based on your example, I passed in the state though and the result is correct! I guess it makes sense since it's an updater function but somehow it had me all confused.

Appreciate it! 🀘🏻

KevinVandy commented 2 years ago

I wrote some docs with a similar approach in my library that is on top of TanStack Table. I recommended the instanceOf Function check, but typeof works too

https://www.material-react-table.com/docs/guides/table-state-management#add-side-effects-in-set-state-callbacks

mouktardev commented 1 year ago

The parameter passed to onRowSelectionChange is either a value or a function. So, I believe that is why setState works as expected.

One possible solution to this problem:

import {
  ...
  RowSelectionState,
  useReactTable,
} from "@tanstack/react-table";

// get table row selection and setter from store or any other source
const rowSelection: RowSelectionState = ...
const myCustomRowSelectionSetter: (rowSelection: RowSelectionState) => void = ... 

...
const table = useReactTable({
  ...
  onRowSelectionChange: (updaterOrValue) => {
    if (typeof updaterOrValue === "function") {
      myCustomRowSelectionSetter(updaterOrValue(rowSelection));
    } else {
      myCustomRowSelectionSetter(updaterOrValue);
    }
  },
  ...

I was wondering why i can't set my state using nanoStore library in onRowSelectionChange , this helped me understand better.