React-table: Override internal table state (beta 26)

Created on 4 Dec 2019  Â·  12Comments  Â·  Source: tannerlinsley/react-table

Describe the bug

Table Options API state.selectedRowPaths: Set<RowPathKey> does not work.

My goal is to provide selection through parent component (making react-table controlled)
but internal state override is not taken into account.

To Reproduce

  1. Go to CodeSandBox
  2. Initial state is OK (5th row is selected)
  3. State override is NOK (3th row not selected)

Expected behavior

When provided, Table Options API state should override internal state.

Desktop (please complete the following information):

  • OS : Windows
  • Browser : Chrome
  • Version : 78

Additional context

BETA react-table 26

Most helpful comment

This is now available in beta.27

All 12 comments

Docs need to be updated still. You can now use the reducer option to override state. I’ll add docs later today.

Tanner Linsley
On Dec 4, 2019, 5:03 AM -0500, Alexis FR notifications@github.com, wrote:

Describe the bug
Table Options API state.selectedRowPaths: Set does not work.
My goal is to provide selection through parent component (making react-table controlled)
but internal state override is not taken into account.
To Reproduce

  1. Go to https://codesandbox.io/s/unruffled-perlman-8bztx
  2. Initial state is OK (5th row is selected)
  3. State override is NOK (3th row not selected)

Expected behavior
When provided, Table Options API state should override internal state.
Desktop (please complete the following information):

• OS : Windows
• Browser : Chrome
• Version : 78

Additional context
BETA react-table 26
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or unsubscribe.

I am facing a somewhat related issue.
In my implementation, I had a "toggle all" button which would collapse/expand all table groups simultaneously. In beta.1 version I implemented it with the help of useTableState hook:

      // setTableState is the second element returned by useTableState hook
      setTableState(actualState => ({
        ...actualState,`
        expanded: isAlreadyExpanded ? [] : getAllExpandedState(rows) // this function collects paths of all rows that should be expanded
      }), ReactTable.actions["toggleExpanded"]);

Now that table state hook is deprecated and there is no toggleExpanded action, I'm trying to implement it with a dispatcher, like this:

      dispatch({
        type: ReactTable.actions.toggleExpandedByPath,
        path: [],
        expanded: isAlreadyExpanded ? [] : getAllExpandedState(rows)
      });

But it doesn't work, probably because toggleExpandedByPath action now requires path parameter. What should I pass to the path parameter if I want to toggle rows in bulk, not one-by-one?
I would appreciate if you help me here, or point me to the right place in the documentation, @tannerlinsley

Hmm, now that you're bringing up this use case, I am starting to wonder if a state override api is worth going back to. The reducer pattern clears up a lot of stuff with the state internally, but what you're talking about is "controlling" the final state (and it would need to be able to be memoized). I'll noodle on this a bit and get back to you.

How does this look? (This is a snippet from the docs I'm writing):

How can I manually control the table state?

Occasionally, you may need to override some of the table state from a parent component or from somewhere above the usage of useTable. In this case, you can turn to useTable's useControlledState option. This hook function is run on every render and allows you an opportunity to override or change the final table state in a safe way.

For example, to control a table's pageIndex from a parent component:

const [controlledPageIndex, setControlledPage] = React.useState(0)

useTable({
  useControlledState: state => {
    return React.useMemo(
      () => ({
        ...state,
        pageIndex: controlledPageIndex,
      }),
      [state, controlledPageIndex]
    )
  },
})

It's important that the state override is done within a useMemo call to prevent the state variable from changing on every render.

This looks like a similar problem i'm having trying to drive state from react-router. Getting table state into history works cleanly, but pushing it back in when the user his the back button has been problematic. I currently push the 'state' option in with a memoized value, this works for pageIndex, but does not seem to work with sortBy.

The above solution appears workable and very close to what I am already doing.

Awesome I think it’s worth shipping and trying out then.

This is now available in beta.27

Thank you @tannerlinsley , it is a solution.

But it means that now I have to take full control over the expanded state of the rows, doesn't it? Previously I could overwrite the array of expanded rows only when needed, i.e. when a user clicked "expand all / collapse all" button. Now I also need to update controlled expanded state when the user clicks on any individual expandable row.

So generic API is available, and that's the most important. But for this particular use-case, which might be quite common (just like select all / unselect all functionality), wouldn't it be nice to have a special action? Or to update semantics of ReactTable.actions.toggleExpandedByPath action to allow for a bulk update? All in all, in beta.1 I could do this using ReactTable.actions.toggleExpanded action

Thanks !
It was this kind of API I was looking for to tune the state.
I tested it successfully for my use case.

A remark : useMemo cannot be used (as described above) in useControlledState because it will "block/memoize" the internal state as long as hooks dependencies does not change.

So if controlledPageIndex does not change, internal state will be kept as is forever...

Something like that will work :

  useTable({
    useControlledState: state => {
      if (state.pageIndex !== controlledPageIndex) {
        return { ...state, pageIndex: controlledPageIndex };
      }
      return state;
    },
  })

Ah yes, you are correct. But you can simply add state to the memoization dependencies.

@afrz have you figured out how to control selection through a parent component? It would be great if there was an example for this.

@serge20 have you tried example given in FAQ ?

Below a constricted sample working in latest version RC15.

//in parent component
 const controlledSelectedRowIds = {
    "id-A": true,
    "id-B": true
  }

//in table definition
useTable({
   useControlledState: state => {
        if (Object.keys(controlledSelectedRowIds).length > 0)
          return { ...state, selectedRowIds: controlledSelectedRowIds };
        return state;
      },
})

In addition, you should use your own select handlers (in your custom table) to maintain selected rows state in your parent component (to update _controlledSelectedRowIds_ in above example) and not use handlers (toggleRowSelected...) provided by the useRowSelect hook.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dilipsundarraj1 picture dilipsundarraj1  Â·  3Comments

LeonHex picture LeonHex  Â·  3Comments

DaveyEdwards picture DaveyEdwards  Â·  3Comments

missmellyg85 picture missmellyg85  Â·  3Comments

bdkersey picture bdkersey  Â·  3Comments