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 normally
const [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 blurred
const onBlur = () => {
updateItem(index, id, value);
}
// If the initialValue is changed external, sync it up with our state
React.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 table
const {
getTableProps, // table props from react-table
getTableBodyProps, // table body props from react-table
headerGroups, // headerGroups, if your table has groupings
rows, // rows for the table based on the data passed
prepareRow // 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?
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 later
editableRowIndex, // 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 }) => (
<button
className="action-button"
onClick={() => {
const currentIndex = row.index;
if (editableRowIndex !== currentIndex) {
// row requested for edit access
setEditableRowIndex(currentIndex);
} else {
// request for saving the updated row
setEditableRowIndex(null); // keep the row closed for edit after we finish updating it
const 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