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>