22

I use Rails and React-Table to display tables. It works fine so far. But How can one add an edit/delete column to the React-Table?

Is it even possible?

return (
    <ReactTable
      data={this.props.working_hours}
      columns={columns}
      defaultPageSize={50}
      className="-striped -highlight"
    />
    )
Trinity76
  • 665
  • 1
  • 6
  • 20

3 Answers3

43

All you need to do is turn columns into a component state. You can see a working example https://codesandbox.io/s/0pp97jnrvv

[Updated 3/5/2018] Misunderstood the question, here's the updated answer:

const columns = [
    ...
    {
       Header: '',
       Cell: row => (
           <div>
               <button onClick={() => handleEdit(row.original)}>Edit</button>
               <button onClick={() => handleDelete(row.original)}>Delete</button>
           </div>
       )
    }
]

where handleEdit and handleDelete will be the callbacks how you want to handle the actions when the buttons are clicked.

Rico Chen
  • 2,260
  • 16
  • 18
1

ReactTable v7

Add a column into a table can be achieved by inserting a column object into columns definitions that pass into useTable hook. Basically, the number of column objects that resides in the columns definitions array represents the number of columns that will be rendered by react-table.

Usually, a minimal column object consists of Header and accessor which at the Header we pass the name of the column and at the accessor, we pass the key that will be used by the react-table to look up the value from the data passed into useTable hook.

{
  Header: "Column Name",
  accessor: "data key",  // can be a nested key
}

Here, to render other than a string inside a cell, which is, in this case, is custom button JSX, we can use either accessor or Cell option and pass to it a Function that returns a valid JSX.

accessor

The document says that accessor accept either string or Function Here we use Function to render a JSX button.

accessor: String | Function(originalRow, rowIndex) => any

One of the benefits of using accessor options is the rowIndex is directly available. The rowIndex represents the index number of rows inside the data array that is currently managed by react-table on the client-side and the originalRow is the raw object of row.

Here the rowIndex can be used as a reference to select and modify row objects in the columns data array.

Cell

Cell option accept Function that return either JSX or React.Component. Cell option is usually used for formatting a cell value, but here we use to render our button.

Cell: Function | React.Component => JSX

The Function receives tableInstance that is quite similar to the result of useTable hook with additional cell, row, and column object.

Cell: (tableInstance) => JSX

Here we can get the row index information too by destructuring the row object:

Cell: (tableInstance) => {
   const { row: index } = tableInstance;
   return (
      ...
   )
}

So, it depends on your requirement in determining whether accessor or Cell will be the chosen one for rendering your edit/add button. But if you need to get more data/information from tableIntance, then Cell is the correct option to go.

Note: If you choose accessor, please make sure that the id is included in the column properties due to the id option being required as the document said so.

Required if accessor is a function. This is the unique ID for the column. It is used by reference in things like sorting, grouping, filtering etc.

Now, we already have the column. The next is the button. Commonly the button is either a normal button that will either call a handler for updating a state or triggering a dialog popup or a link button that will redirect the app to the detail page. So, the code will be:

    // accessor
    {
        Header: 'Action',
        id: 'action',
        accessor: (originalRow, rowIndex) => {
            return (
                // you can pass any information you need as argument
                <button onClick={() => onClickHandler(args)}>
                    X
                </button>
            )
        }
    }

    // or Cell
    {
        Header: 'Action',
        accessor: "action",
        Cell: (tableInstance) => {
            const { row: index } = tableInstance;
            return (
                // you can pass any information you need as argument
                <button onClick={() => onClickHandler(args)}>
                    X
                </button>
            )
        }
    }

Example:

const { useCallback, useEffect, useMemo, useState } = React;
const { useTable } = ReactTable;

// table data
const data = [
    {
        name: "John",
        workingHours: 40
    },
    {
        name: "Doe",
        workingHours: 40
    }
];

const AddEmployee = ({ onSubmit }) => {
    const [name, setName] = useState("");
    const [workingHours, setWorkingHours] = useState("");

    const handleSubmit = (e) => {
        onSubmit(e);
        setName("");
        setWorkingHours("");
    }

    return (
        <fieldset style={{ width: "200px" }}>
            <legend>Add Employee:</legend>
            <form onSubmit={(e) => handleSubmit(e)}>
                <input
                    type="text"
                    name="name"
                    placeholder="Name"
                    value={name}
                    onChange={(e) => setName(e.target.value)}
                />
                <br />
                <input
                    type="text"
                    name="workingHours"
                    placeholder="Working Hours"
                    value={workingHours}
                    onChange={(e) => setWorkingHours(e.target.value)}
                />
                <br />
                <button type="submit">Add</button>
            </form>
        </fieldset>
    )
}

const EditEmployee = ({ row, onSave }) => {
    const { originalRow, rowIndex } = row;
    const [name, setName] = useState(originalRow.name);
    const [workingHours, setWorkingHours] = useState(originalRow.workingHours);

    return (
        <fieldset style={{ width: "200px" }}>
            <legend>Edit Employee:</legend>
            <input
                type="text"
                name="name"
                placeholder="Name"
                value={name}
                onChange={(e) => setName(e.target.value)}
            />
            <br />
            <input
                type="text"
                name="workingHours"
                placeholder="Working Hours"
                value={workingHours}
                onChange={(e) => setWorkingHours(e.target.value)}
            />
            <br />
            <button onClick={() => onSave({ name, workingHours }, rowIndex)}>Save</button>
        </fieldset>
    )
}

function App() {
    const [tableData, setTableData] = useState(data);
    const [editingRow, setEditingRow] = useState();

    const handleDelete = useCallback((index) => {
        setTableData(tableData.filter((v, i) => i !== index));
    },[tableData]);

    const tableColumns = useMemo(() => [
        {
            Header: 'Name',
            accessor: 'name',
        },
        {
            Header: 'Working Hours',
            accessor: 'workingHours'
        },
        {
            Header: 'Action',
            id: 'action',
            accessor: (originalRow, rowIndex) => {
                return (
                    <div>
                        <button onClick={() => setEditingRow({ originalRow, rowIndex })}>
                            Edit
                        </button>
                        <button onClick={() => handleDelete(rowIndex)}>
                            Delete
                        </button>
                    </div>   
                )
            }
        }
    ], [handleDelete]);

    const {
        getTableProps,
        getTableBodyProps,
        headerGroups,
        rows,
        prepareRow
    } = useTable({
        columns: tableColumns,
        data: tableData,
    });

    const handleSubmit = (event) => {
        event.preventDefault();
        const formData = new FormData(event.currentTarget);
        const newData = {};
        formData.forEach((value, key) => newData[key] = value);
        setTableData((prevData) => {
            return [...prevData, newData];
        });
    };

    const handleEdit = useCallback((row, rowIndex) => {
        const editedData = tableData.map((rowData, index) => {
            if (index === rowIndex) {
               return row;
            }
            return rowData;
        });
        setTableData(editedData);
        setEditingRow();
    },[tableData])

    return (
        <div>
            <h3>React-table v.7</h3>
            <br />
            { editingRow ? <EditEmployee row={editingRow} onSave={handleEdit} /> : <AddEmployee onSubmit={handleSubmit} /> }
            <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>
        </div>
    )
}
 
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-table@7.8.0/dist/react-table.development.js"></script>

<div class='react'></div>
yohanes
  • 2,365
  • 1
  • 15
  • 24
0

You can actually add buttons using the accessor prop of the columns in react-table. here is code example:

     {
        Header: 'Action',
        accessor: (originalRow, rowIndex) => (
           <div>
               <button onClick={() => handleEdit(originalRow)}>Edit</button>
               <button onClick={() => handleDelete(originalRow)}>Delete</button>
           </div>
        ),
        id: 'action',
      },