deselecting all rows from higher component

369 views Asked by At

I am using React Table v8 for my new web app. I have a table built with checkboxes for selecting rows. In a component up higher in the component tree I am doing something with the selected rows, and then I want to clear all the selected rows. I found the function toggleAllRowsSelected while searching. And I tried adding this 'useEffect' in to my table component.

useEffect(() => {
    if (selectedRows.length === 0) {
        table.toggleAllRowsSelected(false);
    }
}, [selectedRows]);

And then I passed down 'selectedRows' from my component up higher in the component tree. But that caused a continuous running loop.

I am creating my ReactTable like this:

const table = useReactTable({
    data,
    columns,
    state: {
        sorting,
        rowSelection,
        globalFilter,
        columnFilters
    },
    onSortingChange: setSorting,
    onRowSelectionChange: setRowSelection,
    onColumnFiltersChange: setColumnFilters,
    onGlobalFilterChange: setGlobalFilter,
    enableRowSelection: true,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel()
    // debugTable: true
});

Anyone know how I can properly achieve this?

Basically I just want to pass down a signal/trigger to deselect all rows in the table from a component above the table itself.

4

There are 4 answers

1
Alaa M On BEST ANSWER

I have implemented a react-table (v8) with a button to deselect all rows.
I used ref in the parent component:

const tableRef = React.useRef(null);

After that, I used forwardRef to forward that ref to the child component.

 //Define the methods required for the parent component to interact with the child component
 useImperativeHandle(ref, () => ({
    resetSelectedRows: () => toggleAllRowsSelected(false)
  }));

...

//In the parent component, we can deselect all rows by applying the following
tableRef.current?.resetSelectedRows();

You can review the full functionality in the following CodeSandbox
Edit goofy-cherry-vwjm3c

So using this approach, you can solve the issue you presented.

0
quentinkrammer On

Just from this code snipped I cant tell why you have a render loop.

But it seems that you have 2 components which both keep track of the RowSelectionState:

  • The higher component tracks "selectedRows"
  • The lower component tracks "rowSelection"

This seems problematic. A state should have a single source of truth.

  • Perhaps you could eliminate "selectedRows" and pass down "rowSelection" and "setRowSelection"
  • Use a context to make "rowSelection" and "setRowSelection" available to the react table component
  • store the rowSelection state outside of react by using something like "Jotai"

If the render loop disappears when "strict mode" is turned off, then a useEffect is causing the issue. Otherwise I would suspect the way you are setting "selectedRows" and "rowSelection" is bugged

0
terahertz On

React Tables work by using the useReactTable hook.

 const [rowSelection, setRowSelection] = React.useState({});

 const table = useReactTable({
    state: {
      rowSelection, // the state holding which rows are selected
    },
    enableRowSelection: true, //enable row selection for all rows
    onRowSelectionChange: setRowSelection, // handles row selection change
    // other properties
  });

If row 1 is selected, setRowSelection is called, and the state is updated to { 1: true }.

There are a couple of ways to solve your problem.

Method 1 - shift your state up to parent component

You can lift your state up to your parent components by shifting the rowSelection state up to your parent component.

i.e. shift the following code to your parent component const [rowSelection, setRowSelection] = React.useState({});

then passing it down to your child component like this:

<Table rowSelection={rowSelection} setRowSelection={setRowSelection} />

In your parent component, you can then use setRowSelection({}) to reset the selected rows state to empty.

Here's an example in CodeSandBox demonstrating the above. My example consists of a parent <App> which contains <Table> component and you can reset the React Table in the parent component.

Method 2 - use Context or state management libs

For simplicity's sake, I will use React Context. You'd want to use this if there are many intermediate components between your table and parent component that would be resetting the state. You can also use other state management libraries like Zustand or React Redux instead of Context.

The idea is simple. Since you have already figured out table provides a function, toggleAllRowsSelected(), that would reset all selected rows. Then, all we need to do, is pass the table object "upwards" (to parent component) to utilize it.

To do so with React Context, we first create a TableContext that has setTable as a state.

import { createContext } from 'react';

export const TableContext = createContext({ setTable: null });

In the parent component, we create a table state and wrap the <Table> child component with TableContext.Provider. We also provide the state as default values.

function App() {
  const [table, setTable] = useState();
  
  // other code omitted for brevity
  
  return (
    <TableContext.Provider value={{ setTable }}>
      <Table />
    </TableContext.Provider>
  )

In the child component, we use the hook useContext to read the TableContext and get the setTable function. We create the React Table. When it is created successfully, we set the table state using setTable. The parent component can now access the table object.

function myTable() {
  const { setTable } = useContext(TableContext);

  const reactTable = useReactTable({
    // omitted for brevity
  });

  useEffect(() => {
    if (reactTable) {
      setTable(reactTable); // parent component can now access reactTable
    }
  }, [reactTable, setTable]);
  
  // other code omitted for brevity
}

Here's an example with Codesandbox demonstrating React Context.

0
Afzal K. On

One approach to solving this issue would be to define a new function within your component that takes care of deselecting all rows. When necessary, this function can be invoked from the parent component to clear the selected rows. For instance:

const deselectAllRows = () => {
    table.toggleAllRowsSelected(false);
}

Then, in the parent component, you can utilize the deselectAllRows() function to remove all selected rows:

const clearSelectedRows = () => {
    // perform some action with the selected rows
    // then call the deselectAllRows function
    deselectAllRows();
}

<TableComponent rows={rows} clearSelectedRows={clearSelectedRows} />

toggleAllRowsSelected() function is only triggered when necessary, instead of repeatedly causing a loop.