React.js: using HTML Table as DataGrid for CRUD Operations
import axios from "axios"; export class CategoryService { constructor(){ this.url = "https://catprdapi.azurewebsites.net/api/Category"; } getCategories(){ const response = axios.get(this.url); return response; } getCategory(id){ const response = axios.get(`${this.url}/${id}`); return response; } postCategory(cat){ const response = axios.post(`${this.url}`, cat, { headers: { 'Content-Type':'application/json' } }); return response; } putCategory(id,cat){ const response = axios.put(`${this.url}/${id}`, cat, { headers: { 'Content-Type':'application/json' } }); return response; } deleteCategory(id){ const response = axios.delete(`${this.url}/${id}`); return response; } }
The code in Listing 1, uses the axios package and its HTTP methods to perform Read/Write operations with REST API.
Step 5: In the project folder, add a new folder named Components. In this folder add a new file and name it tablecomponent.js. In this file, we will add code to create EditTableComponent. This component will accept array data from its parent using props and based on it the HTML table code will be generated. The component will have the following features:
• Props
o dataSource: This is a collection input that will be received from the parent component. The EditTableComponent will generate an HTML Table based on the dataSource.
o saveRecord: This is a callback method props type this will be invoked to save either a new record or edit an existing record.
o columns: This props type will be used to pass column names from the parent to child component so that table columns will be generated for these column properties.
o delete: This props type will be used to delete the record.
o canAdd, canEdit, and canDelete : These properties when set to true will be used to generate Add, Edit, and Delete buttons in the EditTableComponent.
• State Properties
o editIndex: This will be used to decide the row to be edited from the HTML table. The value for this state property will be set using the setEditIndex action. The default value for this state property is -1. This means that none of the rows from the table is editable initially when the component is loaded.
o recData: This will be used to add a new empty row in the table. The value for this property will be set using the setRecData action. The default value of this property will be the 0th record from the input collection received from the parent component.
o editRecData: Since the table component will be used to edit the record, we need to read the edited values for all cells for the editing record. This state property will be used to store all edited values for the row in the HTML table. The data for this property will be set using the setEditRecData action. The default value of this property is an empty JSON object ({}).
o tableData: This state property will be used to add a new empty row in the HTML table so that the new record can be added. The data for this property will be set using the setTableData action.
o operationType: This state property is used to decide what type of operation is requested by the components means will it be the new record created or edited an existing record. The value for this property will be set using the setOperationType action.
• Methods for the component
o addNewRecord: This method will be used to add a new Empty row to the HTML table. This method will be bound to the Add New Record HTML button. This method will call the setTableData action to add a new empty object in the tableData state property. This method will set the setOperationType state property to ‘New’.
o editRow: This method will be invoked on the Edit button click that will be shown for each row in the HTML table. This method accepts the id property. This property is an index key from the input received collection based on which the table is generated. Once the Edit button is clicked, the editIndex state property value will be set by invoking the setEditIndex action and then further the record will be filtered from the input received collection based on the id. This filtered record will be set to the editRecData state property by invoking the setEditRectData action. This method will set the setOperationType state property to ‘Edit.
o handleChanges: This method will be used to read values for each editable row. When the Edit button is clicked, that specific row will show Text Elements to edit values. These Text elements will be bound to the handleChanges method so that edited values can be read. One important behavior of this method is that the same method will be used for adding new records as well as editing an existing record. As discussed in the editRow method when the Edit button is clicked the row will show Text elements at the same time when the Add New Record button is clicked the new empty row will be appended to the HTML table with the Edit button in it. This means that we need to click on the Edit button to add a new record as well as edit an existing record, we will do this by toggling across recData and editRecData state properties. When editRecData is undefined it means that the new record will be created else an existing record will be updated. The handleChanges method will set values in these two state properties based on either adding a new record or editing an existing record.
o saveRow: This is the most important method. This method will be invoked on the Save button click. This method will be used to either create a new record or edit an existing record based on the recData or editRecData respectively. This method will use the saveRecord props type to emit a save request to the parent component. This method will emit the operation type either as Save to Edit to the parent component using the saveRecord props type to either perform an operation to create a New record or Edit an existing record.
o deleteRow: This method will be invoked on the Delete button. This will be used to emit the delete request to the parent component by invoking the saveRecord props type.
import { useEffect, useState } from "react"; export const EditTableComponent=(props)=> { // The edit index to -1 const [editIndex, setEditIndex] = useState(-1); // The record that will be bound with the Table row // const [recData, setRecData] = useState(props.dataSource[0]); const [recData, setRecData] = useState(Object.create(props.dataSource[0])); // The State Object for the Edit const [editRecData, setEditRecData] = useState({}); // Data to be displayed in the HTML Table const [tableData, setTableData] = useState(props.dataSource); const [operationType, setOperationType] = useState(''); const editRow = (id)=>{ setEditIndex(id); // Read Old Valeus for the Edit Clicked Rows let rec = props.dataSource.filter((r,i)=>{ return Object.values(r)[0] === id; })[0]; setEditRecData(rec); // Set the OPerationType as 'Edit' setOperationType('Edit'); } const cancelEdit = ()=>{ setEditIndex(-1); } const saveRow=()=>{ if(editRecData === undefined){ alert('Save New'); props.saveRecord(recData,'Save'); } else { props.saveRecord(editRecData,'Edit'); } setEditIndex(-1); setOperationType(''); }; const deleteRow = (rec)=>{ props.delete(rec); } const handleChanges =(evt)=>{ if(editRecData === undefined) { setRecData({...recData, [evt.target.id]:evt.target.value}); } else { setEditRecData({...editRecData, [evt.target.id]:evt.target.value}); } }; const addNewRecord=()=>{ //tableData.push({}); setTableData([...tableData, {}]); setOperationType('New'); }; if(props.dataSource === undefined || props.dataSource.length === 0){ return( <div className="alert alert-danger"> <strong>No Data to Show In Table Component</strong> </div> ); } else { return ( <div className="container"> <div className="container" hidden={!props.canAdd}> <button className="btn btn-primary" onClick={addNewRecord} >Add New Record</button> </div> <table className="table table-bordered table-striped"> <thead> <tr> { props.columns.map((column,index)=>( <th key={index}>{column}</th> )) } </tr> </thead> <tbody> { tableData.map((record,idx)=>( Object.values(record)[0] === editIndex ? <tr key={idx}> { props.columns.map((column,index)=>( <td key={index}> <input value={record.column} placeholder={record[column]} id={column} onChange={handleChanges} /> </td> )) } <td> <button className="btn btn-success" onClick={saveRow} >Save</button> </td> <td> <button className="btn btn-primary" onClick={cancelEdit} >Cancel</button> </td> </tr> : <tr key={idx}> { props.columns.map((column,index)=>( <td key={index}>{record[column]}</td> )) } <td hidden={!props.canEdit}> <button className="btn btn-warning" onClick={()=>editRow(Object.values(record)[0])}>Edit</button> </td> <td hidden={!props.canDelete}> <button className="btn btn-danger" onClick={()=>deleteRow(Object.values(record)[0])}>Delete</button> </td> </tr> )) } </tbody> </table> </div> ); } }
import { Employees } from './models/data'; import { EditTableComponent } from './components/tablecomponent'; import { CategoryService } from './catservice'; import { useEffect, useState } from 'react'; function App() { const [categories, setCategories] = useState([]); const [columns, setColumns] = useState([]); const [category, setCategory] = useState({ CategoryId: 0, CategoryName: '', BasePrice: 0, }); const serv= new CategoryService(); useEffect(()=>{ serv.getCategories().then(resp=>{ setCategories(resp.data); setColumns(Object.keys(resp.data[0])); }).catch(error=>{ console.log(`Error : ${error}`); }) } ,[]); const save=(rec, operation)=>{ if(operation === 'Save'){ alert(`In Save New ${JSON.stringify(rec)}`); serv.postCategory(rec) .then(response=>{ return response.data; }).then(data=>{ setCategories(data); window.location.reload(true); }) .catch(error=>{ console.log(`Error occurred while saving record: ${error}`); }); } if(operation === 'Edit'){ alert(`In Edit ${JSON.stringify(rec)}`); serv.putCategory(rec.CategoryId,rec) .then(response=>{ return response.data; }).then(data=>{ setCategories(data); window.location.reload(true); }) .catch(error=>{ console.log(`Error occurred while saving record: ${error}`); }); } }; const deleteRecord=(rec)=>{ alert(`Record to be deleted :${JSON.stringify(rec)}`); serv.deleteCategory(rec).then(response=>{ return response.data; }).then(data=>{ window.location.reload(true); }) .catch(error=>{ console.log(`Error occurred while saving record: ${error}`); }); }; if(categories === undefined || categories.length === 0){ return ( <div className='alert alert-danger'> <strong>No data to show in Main</strong> </div> ); } else { return ( <div className="container"> <h1>React Editable Grid</h1> <EditTableComponent dataSource={categories} saveRecord={save} delete={deleteRecord} canDelete={true} canEdit={true} columns = {columns} canAdd={true} /> </div> ); } } export default App;