6

I'm trying to pass the current selection in my react-table component up to the parent to do some logic when a selection has occurred, but I can't seem to find a way to reliably get the selection. I've tried the following:

function Table({ columns: userColumns, data, setSelection }) {
  const {
      ...,
      selectedFlatRows,
    } = useTable(
      {
        ...
      },
       ...,
      useRowSelect,
      (hooks) => {...})

  useEffect(() => {

      // Bubble up the selection to the parent component
      setSelection(selectedFlatRows.map((row) => row.original));
   }, [selectedFlatRows]);

But the above just creates an infinite loop. I've built the table selection based on the guidance given in the docs, but they dont seem to cover how to get the selected row reliably.

Chris
  • 7,830
  • 6
  • 38
  • 72

4 Answers4

3

Whilst rotimi-best's answer is correct, it didn't solve my issue. I had a containing function component which needed to know about the selection. My structure was as follows:

<Container> // This container needs to know about the selection.
   <Table
      columns={columns}
      data={data}
      setSelection={(selection) => handleSetSelection(selection)} // Passed down to the table instance
   />
</Container>

The solution was to wrap the top level setSelection in a useCallback. This, together with using memoization as outlined by the react-table docs solved the issue.

Container:
...
const [selection, setSelection] = useState([]);
const selectionCallback = React.useCallback((ids) => setSelection(ids), [
    setSelection,
  ]);
Chris
  • 7,830
  • 6
  • 38
  • 72
2

I don't get an infinite loop. The sandbox doc you mentioned in your question, I implemented what you requested and every thing worked well. Please take a look at the codesandbox and the short snippet I took from the sandbox.

import React from 'react'
import styled from 'styled-components'
import { useTable, useRowSelect } from 'react-table'

import makeData from './makeData'

function Table({ columns, data, setSelection }) {
  // Use the state and functions returned from useTable to build your UI
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    selectedFlatRows,
    state: { selectedRowIds },
  } = useTable(
    {
      columns,
      data,
    },
    useRowSelect,
    hooks => {
      hooks.visibleColumns.push(columns => [
        // Let's make a column for selection
        {
          id: 'selection',
          // The header can use the table's getToggleAllRowsSelectedProps method
          // to render a checkbox
          Header: ({ getToggleAllRowsSelectedProps }) => (
            <div>
              <IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
            </div>
          ),
          // The cell can use the individual row's getToggleRowSelectedProps method
          // to the render a checkbox
          Cell: ({ row }) => (
            <div>
              <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
            </div>
          ),
        },
        ...columns,
      ])
    }
  )

  React.useEffect(() => {
    // Bubble up the selection to the parent component
    setSelection(selectedFlatRows.map((row) => row.original));
  }, [setSelection, selectedFlatRows]);

  console.log('selectedFlatRows', selectedFlatRows)

  // Render the UI for your table
  return (
    <>
      <table {...getTableProps()}>
        <thead>
          {headerGroups.map(headerGroup => (
            <tr {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map(column => (
                <th {...column.getHeaderProps()}>{column.render('Header')}</th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody {...getTableBodyProps()}>
          data here
        </tbody>
      </table>
    </>
  )
}

function App() {
  const [selection, setSelection] = React.useState([]);
  const columns = []

  const data = React.useMemo(() => makeData(10, 3), [])
  console.log('selection', selection)
  return (
    <Styles>
      <Table columns={columns} data={data} setSelection={setSelection} />
    </Styles>
  )
}

export default App
rotimi-best
  • 1,852
  • 18
  • 29
  • I found a similar solution with global store `useEffect(() => { if (isSelectable && selectedFlatRows) { const rows = selectedFlatRows.map((row) => row.original); context.dispatch({ action: "selectedFlatRows", data: rows }); } }, [selectedFlatRows]);` I don't get the use of `setSelection` as a dependency and how it affects things there, shouldn't be `selection`? – A. D'Alfonso Nov 18 '22 at 22:19
0

None of the above (or anywhere else on the interwebs) worked for me but here's what did. And I imagine, with the number of people who've asked this same question about React Table v7, that in version 8 of React Table, the state, especially state having to do with selection, has been ironed out. But anyway this is the ONLY workaround that worked for me, loosely based on this codesandbox.

// Parent component

  1. I used a useCallback hook to prevent my setSelectedRows (your setSelection) function being passed to the child as a new function and kicking off multiple renders.

  2. And I passed in an initial state that sets React Table's selectedRowIds with the parent's selectedRows:

const ParentDoingParentyThings = () => {
  const [selectedRows, setSelectedRows] = React.useState({});
  const handleSelection = React.useCallback((value) => {
    setSelectedRows(value);   
    }, []); 

return (
  <Table
        data={tableData}
        setSelectedRows={handleSelection}
        initialState={{ sortBy: [{ id: 'totalSeries', desc: true }], selectedRowIds: selectedRows }}
        ...
      />
  )
}

// And the Table / child Then!...

  1. I use this lesser know function from React Table called useMountedLayoutEffect. Dont ask me in detail what it does, but it works, that's what it does....
  2. Then I pull from RT's selectedRowIds state
  3. And I use RT's useMountedLayoutEffect function like I would a useEffect
import {
  ...
  useMountedLayoutEffect,
} from 'react-table';

export const Table = ({
  data,
  setSelectedRows,
  initialState,
  ...
}: Props) => {
  ...
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state: { selectedRowIds },
  } = useTable(
    {
      data: tableData,
      initialState,
      ...
    },
    useSortBy,
    useRowSelect,
  );

  useMountedLayoutEffect(() => {
    setSelectedRows && setSelectedRows(selectedRowIds);
  }, [setSelectedRows, selectedRowIds]);

  return (
    <table {...getTableProps()} className={className}>
      ...
    </table>
Cat Perry
  • 944
  • 1
  • 11
  • 15
0

Define columns and/or your data with React.useMemo to avoid infinite loop.

const columns = React.useMemo(
  () => [
    {
      Header: 'Column 1',
      accessor: 'col1', // accessor is the "key" in the data
    },
    {
      Header: 'Column 2',
      accessor: 'col2',
    },
  ],
  []
)
kenvilar
  • 21
  • 5