I am making the interactive table using react-table on my application.

My goal is to make a table that can be editable per row when clicking on a button in a table cell.

I designed an EditableCell like the following.

import React, {useState} from "react";export const EditableCell = ({value: initialValue,row: Row,column: {id, editable, state},isEditing,updateItem, // This is a custom function that we supplied to our table instance}) => {// We need to keep and update the state of the cell normallyconst [value, setValue] = React.useState(initialValue);const {index} = Row;const onChange = e => {setValue(e.target.value);};// We'll only update the external data when the input is blurredconst onBlur = () => {updateItem(index, id, value);}// If the initialValue is changed external, sync it up with our stateReact.useEffect(() => {setValue(initialValue)}, [initialValue]);/*** Event handler to make a row editable.* @param e*/const setRowEditing = (e) => {// TODO};let retObj = null;if (isEditing && editable) {switch (id) {default:retObj = <input className="input-edit w-100" value={value} onChange={onChange} onBlur={onBlur}/>;break;}} else {switch (id) {case 'action_btn':retObj = <><button className="btn btn-sm btn-info btn-sm-td" onClick={setRowEditing}>{ isEditing? "Save" : "Edit"}</button></>;break;default:retObj = <div>{value}</div>;break;}}return retObj;}export const defaultColumn = {Cell: EditableCell,};

And my table's definition is: (I used my EditableCell component)

export default function Table({ columns, data, updateItem }) {// Use the useTable Hook to send the columns and data to build the tableconst {getTableProps, // table props from react-tablegetTableBodyProps, // table body props from react-tableheaderGroups, // headerGroups, if your table has groupingsrows, // rows for the table based on the data passedprepareRow // Prepare the row (this function needs to be called for each row before getting the row props)} = useTable({columns,data,defaultColumn,updateItem,}, useBlockLayout, useRowState);/*Render the UI for your table- react-table doesn't have UI, it's headless. We just need to put the react-table props from the Hooks, and it will do its magic automatically*/return (<table className="data-table" {...getTableProps()}><thead>{headerGroups.map(headerGroup => (<tr {...headerGroup.getHeaderGroupProps()}>{headerGroup.headers.map(column => (<th {...column.getHeaderProps()}>{column.render("Header")}</th>))}</tr>))}</thead><tbody {...getTableBodyProps()}>{rows.map((row, i) => {prepareRow(row);return (<tr {...row.getRowProps()}>{row.cells.map(cell => {return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;})}</tr>);})}</tbody></table>);};

In setRowEditing function, I am going to change the state of the current row or its cells so that cells in the row are rendered as the input field or whatever.

But I don't know how to change the state properly.

Could you advise on this?

2

Best Answer


In your column array that you pass into react table you need to create a button who's onClick function takes a callback to edit your data to add an isEditing: true so you will handle turning the row to edit mode from outside of the table. No need for setRowEditing in an editable cell.

function to set the table data with 'isEditing' property

const handleClickEditRow = (rowIndex) => {setTableData(prev => prev.map((r, index) => ({...r, isEditing: rowIndex === index})))}

in your columns

{accessor: '[editButton]',Cell: (cellObj) => <button onClick={() => handleClickEditRow(cellObj.row.index)}>Edit</button>}

I tried to achieve this feature/functionality by doing the following steps:

// leverage useState react hook. const [editableRowIndex, setEditableRowIndex] = React.useState(null);

Our initial value for the state variable is null - so that all the rows are not open for edit.

Pass the state variable and method to the useTable() root hook.custom plugin hooks and other variables/methods maintaining the component state are returned from the table instance. These you can later retrieve from anywhere you want.

You can render the Editable Cell Component and custom plugin hook (edit hook, in this case) according to the state variables.

const {// all your hooks...} = useTable({columns,data,// all your other hooks...updateMyData,// pass state variables so that we can access them in edit hook latereditableRowIndex, // index of the single row we want to edit setEditableRowIndex // setState hook for toggling edit on/off switch},// other hooks... (hooks) => {hooks.allColumns.push((columns) => [// other hooks such as selection hook...columns,// edit hook{accessor: "edit",id: "edit",Header: "edit",Cell: ({ row, setEditableRowIndex, editableRowIndex }) => (<buttonclassName="action-button"onClick={() => {const currentIndex = row.index;if (editableRowIndex !== currentIndex) {// row requested for edit accesssetEditableRowIndex(currentIndex);} else {// request for saving the updated rowsetEditableRowIndex(null); // keep the row closed for edit after we finish updating itconst updatedRow = row.values;console.log("updated row values:");console.log(updatedRow);// call your updateRow API}}}>{/* single action button supporting 2 modes */}{editableRowIndex !== row.index ? "Edit" : "Save"}</button>)}]);});

An example can be found in the following links:

code sandbox link:https://codesandbox.io/s/github/smmziaul/only-one-row-editable

github repo link:https://github.com/smmziaul/only-one-row-editable