const { useState } = React;
const data = [
{ id: 1, name: 'Rita', age: 22 },
{ id: 2, name: 'Sue', age: 42 },
{ id: 3, name: 'Bob', age: 12 }
]
function Example({ data }) {
// Have states for the data, the current selected row
// and the data that is currently being edited
const [ rowData, setRowData ] = useState(data);
const [ selectedRow, setSelectedRow ] = useState(0);
const [ editData, setEditData ] = useState({});
// Compiles the row data
function getRows() {
return rowData.map((obj, i) => {
// Get the id, name, and age from the
// current object
const { id, name, age } = obj;
// If the id of the current object equals
// the selected row return a set of row data with input
// elements (not id though) auto-populated with the
// information from that object
if (id === selectedRow) {
return (
<tr>
<td>{editData.id}</td>
<td>
<input
className="edit"
type="text"
name="name"
value={editData.name}/>
</td>
<td>
<input
className="edit"
type="text"
name="age"
value={editData.age}
/>
</td>
<button
data-id={id}
data-action="Save"
>Save
</button>
</tr>
);
}
// Otherwise return a set of non-editable
// row data
return (
<tr>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<button
data-id={id}
data-action="Edit"
>Edit
</button>
</tr>
);
});
}
// When a button has been clicked
// Note: because we're using event delegation
// (attaching listeners to the table element instead
// of all the rows) we need to check that element
// we clicked on is the button
function handleClick(e) {
const { nodeName } = e.target;
if (nodeName === 'BUTTON') {
const { id, action } = e.target.dataset;
// If the button action is edit, set the
// selected row state, and auto-populate the
// editData state
if (action === 'Edit') {
setSelectedRow(+id);
const obj = data.find(obj => obj.id === +id);
setEditData({...obj});
}
// If the action is save filter out the matching
// object from the data state, and then update the data
// with the filtered object, plus the object in
// the editData state, making sure you sort by id
// otherwise the edited row will appear at the bottom
// Then reset the states
if (action === 'Save') {
const filtered = data.filter(obj => obj.id !== selectedRow);
const updated = [...filtered, editData];
updated.sort((a, b) => a.id - b.id);
setRowData(updated);
setSelectedRow(0);
setEditData({});
}
}
}
// When an input changes
// Again we need to check that the element
// that has changed is an input
function handleChange(e) {
const { nodeName } = e.target;
// Update the editData state using the input
// name and value
if (nodeName === 'INPUT') {
const { name, value } = e.target;
setEditData({ ...editData, [name]: value });
}
}
// We attach two listeners to the table element
// One to catch clicks from the button
// The other to catch changes to the inputs if
// we're in edit mode
return (
<table
onClick={handleClick}
onChange={handleChange}
>
<tr class="heading">
<td>ID</td>
<td>Name</td>
<td>Age</td>
</tr>
<tbody>
{getRows()}
</tbody>
</table>
);
};
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
table { border: 1px solid #898989; border-collapse: collapse; }
td, input { width: 75px; text-align: center; }
td { padding: 0.2em; border: 1px solid #898989; }
.heading { background-color: #efefef; text-align: center; font-weight: 600; }
.edit { background-color: #ffffcc; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>