Make only one row selectable at a time in React Table 7.1.0
Asked Answered
F

9

7

I am trying to implement react table with just one row selectable at at time. I have gone through a lot of examples for multiple rows selection in a react table but in my case, the user can select only one row when the user clicks on the radio button but currently all the rows can be selected. Could anyone help me out on this to implement ?

enter image description here

Flyblow answered 8/7, 2020 at 14:49 Comment(4)
Is it necessarly a react-table issue? I'd just use some component-level or global state management for it using item ids.Marris
@Marris I need to use react table to render checkbox, toggle checkbox selection when clicked on radio button, so need to do these operations on react table , could you elaborate on your suggestions ? I am kinda stuck at this point ..Flyblow
I'm not a huge fan of react table to be honest. You can set up complex tables very easy but you waste all this time as soon as you are trying to add a custom feature. That was my experience.Marris
@Marris yeah i agree, but all the other features are all already integrated with react table , only this one is yet to be completed.. so this has become a serious problem for now .. so just trying to find the solutionFlyblow
D
13

I know this is an old question, but maybe someone will find this solution useful. Since version 7, react-table provides a stateReducer that can be used to track and change the state of a table. (before v7 it had reducerHandlers, but I didn't go deep into that). You can modify the state as follows:

useTable(
    {
      columns,
      data,
      stateReducer: (newState, action) => {
          if (action.type === "toggleRowSelected") {
            newState.selectedRowIds = {
              [action.id]: true
            }
          }

          return newState;
      },
    }
    ...

Here is the CodeSandbox with the changes described

Deland answered 6/7, 2021 at 17:47 Comment(1)
This should be the correct answer.Caylor
K
4

Using react-table 7.5.0, I've put together a CodeSandbox with a react-table that functionally makes only one row selectable at a time.

In essence, I replaced an unconditionally rendered checkbox:

Cell: ({ row }) => (
  <div>
    <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
  </div>
)

with a conditionally rendered checkbox:

Cell: ({ row }) => {
  if (
    rows.filter((row) => row.isSelected).length < 1 ||
    row.isSelected
  ) {
    return (
      <div>
        <IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
      </div>
    );
  } else {
    return (
      <div>
        <IndeterminateCheckbox
          checked={false}
          readOnly
          style={row.getToggleRowSelectedProps().style}
        />
      </div>
    );
  }
}

I filtered all row objects to check for selected rows and then I conditionally rendered a normally functioning react-table checkbox if the number of selected rows is less than 1 or if the row is already selected.

If the number of checked rows is at least one and the row isn't selected, I render a read only checkbox that can't be selected.

Ideally, I would have liked to use react-table's built-in selectedRowIds instead of filtering through all row objects, but I couldn't figure out how to implement useTable() in a manner that allows me to refer to it since it is derived from it.

Here is react-table's Row Selection CodeSandbox that I forked from. Here's the relevant page in their docs.

I'll move the code into a built-in code snippet at a later time.

Knucklebone answered 19/8, 2020 at 23:10 Comment(2)
very useful! should be the accepted answer :)Jaffa
It would be the accepted solution if it un-checked the current selected row before checking the new one instead of stopping you from selecting the new one. That's not user friendly in my opinion.Dipper
Q
3
Cell: ({row}) => (
   <IndeterminateCheckbox
      {...row.getToggleRowSelectedProps({
         onChange: () => {
           const selected = row.isSelected; // get selected status of current row.
           toggleAllRowsSelected(false); // deselect all.
           row.toggleRowSelected(!selected); // reverse selected status of current row.
         },
      })}
   />
)
Quant answered 30/3, 2022 at 20:8 Comment(1)
This is leaner and it behaves more like a data-grid. Thanks!Dipper
V
2

For me cleaning selected rows before select a new one worked. Version: "@tanstack/react-table": "^8.7.4",

Cell: ({ row, table }) => (
   <IndeterminateCheckbox
      {...row.getToggleRowSelectedProps({
         onChange: () => {
           table.resetRowSelection()
           row.toggleSelected()
         },
      })}
   />
)
Villus answered 12/4, 2023 at 21:44 Comment(0)
B
1

I've notice that @Ann answers works great with minor issue - if you want to toggle off selected row, it won't work.

I've added a validation to fix this:

useTable(
    {
      columns,
      data,
      stateReducer: (state, action) => {
         if (action.type === 'toggleRowSelected' && Object.keys(state.selectedRowIds).length) {
            const newState = { ...state };

            newState.selectedRowIds = {
              [action.id]: true,
            };

            return newState;
         }

         return state;
      },
    }
    ...
)
Beebread answered 25/7, 2022 at 16:46 Comment(2)
A good fix but the above example needs a small changes in the variable, instead of defining the object as newState it should be state Example: Object.keys(state.selectedRowIds)Stammer
You're right! The fix is fixed :)Beebread
S
0

This is an old one, but here is my solution:

Get toggleAllRowsSelected from table instance.

  const {
    allColumns,
    getTableBodyProps,
    getTableProps,
    headerGroups,
    prepareRow,
    rows,
    state,
    toggleAllRowsSelected,
  } = useTable(
    {
      columns,
      data,
    },
    useRowSelect
  );

Then add onClick like in code below to your tr-component (in my code tr has been styled with emotion and named StyledTableRow). This will first set all selected rows to false and then toggle current row isSelected value if it was false initially, if it was true then it has already been set to false.

If you don't want to allow clicking selected row to unselect it (eg. for radio buttons), just use the isSelected === true to block any action here.

{rows.map((row, i) => {
            prepareRow(row);
            // const isRowSelected = isSelected(row.id);
            const { isSelected, getRowProps, getToggleRowSelectedProps, toggleRowSelected } = row;
            const {
              onChange,
              indeterminate,
              ...toggleRowSelectedProps
            } = getToggleRowSelectedProps();
            console.log(row);
            return (
              <StyledTableRow
                hover
                {...getRowProps()}
                {...toggleRowSelectedProps}
                selected={isSelected}
                onClick={() => {
                  const current = isSelected;
                  toggleAllRowsSelected(false);
                  if (!current) {
                    toggleRowSelected();
                  }
                }}
              >
                {row.cells.map((cell, key) => (
Santiago answered 10/2, 2022 at 11:50 Comment(0)
B
0

On TanStack Table v8, for me the easiest approach was to adjust the onRowSelectionChange parameter of useReactTable.

I just updated it to reset the local rowSelection state to an empty object first:

const table = useReactTable({
    columns,
    data: tableData,
    state: {
        rowSelection,
    },
    enableRowSelection: true,
    onRowSelectionChange: stateUpdater => {
        setRowSelection({}); // <-- First reset the current selection
        setRowSelection(stateUpdater);
    },
    getCoreRowModel: getCoreRowModel(),
});

Here is a sandbox as well:

https://codesandbox.io/s/divine-shadow-9sw2xm?file=/src/App.js

Brie answered 4/7, 2023 at 10:13 Comment(0)
M
-1

This is a simple react implementation of "radio-like" behaviour with useReducer to demonstrate how to use state management with table.

const { useReducer } = React; // --> for inline use
// import React, { useReducer } from 'react';  // --> for real project


const reducer = (state, action) => {
  return { checkedId: action.id }
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, {})
  
  const CheckBox = ({id}) => (
    <input
      id={id}
      onClick={() => dispatch({ id })}
      checked={state.checkedId === id}
      type="checkbox"
    />
  )

 
  return (
    <table border="1">
      <tr><td><CheckBox id="1" /></td><td>John</td></tr>
      <tr><td><CheckBox id="2" /></td><td>Doe</td></tr>
    </table>
  )
};


ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Marris answered 9/7, 2020 at 8:37 Comment(0)
N
-1

You can also override the onChange method on the Cell(...) prop function.

https://react-table.tanstack.com/docs/api/useRowSelect#row-properties

getToggleRowSelectedProps: Function(props) => props
Use this function to get the props needed for a select row checkbox.
Props:
onChange: Function()
style.cursor: 'pointer'
checked: Bool
title: 'Toggle Row Selected'

I'll give a sandbox later when I have the extra time.

Nitrobacteria answered 1/10, 2020 at 16:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.