How to make the row editable when clicking on an "Edit" button on a cell with react-table
Asked Answered
B

2

11

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?

Bluish answered 7/10, 2020 at 6:33 Comment(3)
Do you have a working example of this scenario? I am trying to implement something similar but not able to :( I would really appreciate if you could share a working example of how you fixed your issueThermic
Hi i am also trying to do same thing but stuck in updating data...if anyone solve this type of example then please share thanks @NickDivConvulsant
@Convulsant I definitely will. I personally changed the library because the business requirement changed with time but I will still give it another try to see if I can make it work.Thermic
K
4

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

Kalikalian answered 20/3, 2022 at 8:29 Comment(0)
C
6

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>
}
Cathrinecathryn answered 17/11, 2020 at 20:18 Comment(2)
thanks for your solution i did same thing but its editing column not rows.. and after some code change i m editing row but still faces issue with updating dataConvulsant
What does '[editButton]' mean? Is it just an arbitrary string to help the reader? Or something special?Theda
K
4

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

Kalikalian answered 20/3, 2022 at 8:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.