Dynamic React Table With Add And Delete Row In SharePoint Framework (SPFx)

Today, we will learn how to create a dynamic React table and add and delete rows using SharePoint Framework (SPFx). React table has options to customize the row limit based on your business needs. React table is a reusable component that can be used by changing only the column properties. I am using "Office UI Fabric React" controls to style the button controls.
 
Code Usage
 
The "ReactTable.tsx" reusable component can be written as below.
  1. /* import react and office ui fabric */      
  2. import * as React from 'react';      
  3. import { IHelloWorldProps } from '../IHelloWorldProps';      
  4. import {  DefaultButton,  PrimaryButton, DialogType, Dialog, DialogFooter,Icon,Label, TextField, ActionButton,autobind  } from 'office-ui-fabric-react';     
  5.     
  6.     
  7. /* Initial loading of table column properties */      
  8. const tableColProps=[{    
  9.       id:1,    
  10.       NounName:"",    
  11.       CurrentPN:"",    
  12.       NewPart:"",    
  13.       Option:"",    
  14.       Supplier:"",    
  15.       EJR:""    
  16.     }]    
  17.     
  18.     
  19.     
  20. /* Declare React Table State variables */      
  21. export interface IReactTableState {    
  22.       tableHeaders:any;    
  23.       rows:any;    
  24.       rowLimit:number;    
  25.       addButtonText:string;    
  26.       removeButtonText:string;        
  27.   }    
  28.     
  29.     
  30. export default class ReactTable extends React.Component<IHelloWorldProps,IReactTableState> {    
  31.    
  32.     /* React Table constructor */    
  33.     constructor(props:  IHelloWorldProps) {    
  34.         super(props);    
  35.           this.state = {    
  36.               tableHeaders:["Noun Name""Current PN""New Part""Option#""Supplier""EJR"],    
  37.               rows:this.props.renderTable,    
  38.               rowLimit:20,    
  39.               addButtonText :"Add New Row",    
  40.               removeButtonText : "Delete Row"                
  41.           };        
  42.       }    
  43.     
  44.       
  45. /* This event will fire on cell change */    
  46.     handleChange = (index) => evt  => {    
  47.         try {    
  48.               var item = {    
  49.                   id: evt.target.id,    
  50.                   name: evt.target.name,    
  51.                   value: evt.target.value    
  52.               };    
  53.               var rowsArray = this.state.rows;    
  54.               var newRow = rowsArray.map((row, i) => {    
  55.                   for (var key in row) {    
  56.                       if (key == item.name && row.id == item.id) {    
  57.                           row[key] = item.value;    
  58.                       }    
  59.                   }    
  60.                   return row;    
  61.               });    
  62.               this.setState({ rows: newRow });    
  63.               this.props.tableData(rowsArray);    
  64.           } catch (error) {    
  65.               console.log("Error in React Table handle change : " + error);    
  66.           }           
  67.       };    
  68.     
  69.     
  70.     
  71.     /* This event will fire on adding new row */    
  72.     handleAddRow = () => {    
  73.         try {    
  74.             var id = (+ new Date() + Math.floor(Math.random() * 999999)).toString(36);    
  75.             const tableColProps = {    
  76.                   id: id,    
  77.                   NounName: "",    
  78.                   CurrentPN: "",    
  79.                   NewPart: "",    
  80.                   Option: "",    
  81.                   Supplier: "",    
  82.                   EJR: ""    
  83.               }    
  84.              if (this.state.rows.length < this.state.rowLimit) {    
  85.                  this.state.rows.push(tableColProps);    
  86.                  this.setState(this.state.rows);    
  87.              } else {    
  88.                  alert('Add row limit exceeds');    
  89.              }    
  90.          } catch (error) {    
  91.              console.log("Error in React Table handle Add Row : " + error)    
  92.          }    
  93.      };    
  94.     
  95.     
  96.     
  97.     /* This event will fire on remove row */    
  98.     handleRemoveRow = ()  => {    
  99.         try {    
  100.              var rowsArray = this.state.rows;    
  101.              if (rowsArray.length > 1) {    
  102.                 var newRow = rowsArray.slice(0, -1);    
  103.                 this.setState({ rows: newRow });    
  104.             }    
  105.             this.props.tableData(newRow);    
  106.         } catch (error) {    
  107.             console.log("Error in React Table handle Remove Row : " + error);    
  108.         }    
  109.     };    
  110.     
  111.     
  112.     
  113.     /* This event will fire on remove specific row */    
  114.     handleRemoveSpecificRow = (idx) => () => {    
  115.         try {    
  116.             const rows = [this.state.rows]    
  117.             if(rows.length > 1){    
  118.                 rows.splice(idx, 1);    
  119.             }    
  120.     
  121.             this.setState({ rows });    
  122.         } catch (error) {    
  123.             console.log("Error in React Table handle Remove Specific Row : " + error);    
  124.         }    
  125.     }    
  126.     
  127.    /* This event will fire on next properties update */    
  128.     
  129.     componentWillReceiveProps(nextProps) {    
  130.         try{    
  131.             if (nextProps.renderTable.length > 0) {    
  132.                 this.setState({ rows: nextProps.renderTable });    
  133.             }else{    
  134.                 this.setState({ rows: tableColProps });    
  135.             }    
  136.     
  137.         }catch(error){    
  138.     
  139.             console.log("Error in React Table component will receive props : " + error);    
  140.    
  141.         }            
  142.     }       
  143.     
  144. render() {    
  145.    
  146.        let list = this.state.rows.map((item,idx) =>{    
  147.             return (    
  148.                 <tr key={idx}>    
  149.                     <td>    
  150.                         <input    
  151.                             type="text"    
  152.                             name="NounName"    
  153.                             value={this.state.rows[idx].NounName}    
  154.                             onChange={this.handleChange(idx)}    
  155.                             id={this.state.rows[idx].id}    
  156.                         />    
  157.                     </td>    
  158.     
  159.                     <td>    
  160.                         <input    
  161.                             type="text"    
  162.                             name="CurrentPN"    
  163.                             value={this.state.rows[idx].CurrentPN}    
  164.                             onChange={this.handleChange(idx)}    
  165.                             id={this.state.rows[idx].id}    
  166.                         />    
  167.                     </td>    
  168.     
  169.                     <td>    
  170.                         <input    
  171.                             type="text"    
  172.                             name="NewPart"    
  173.                             value={this.state.rows[idx].NewPart}    
  174.                             onChange={this.handleChange(idx)}    
  175.                             id={this.state.rows[idx].id}    
  176.                         />    
  177.                     </td>    
  178.     
  179.                     <td>    
  180.                         <input    
  181.                             type="text"    
  182.                             name="Option"    
  183.                             value={this.state.rows[idx].Option}    
  184.                             onChange={this.handleChange(idx)}    
  185.                             id={this.state.rows[idx].id}    
  186.                         />    
  187.                     </td>    
  188.     
  189.                     <td>    
  190.                         <input    
  191.                             type="text"    
  192.                             name="Supplier"    
  193.                             value={this.state.rows[idx].Supplier}    
  194.                             onChange={this.handleChange(idx)}    
  195.                             id={this.state.rows[idx].id}    
  196.                         />    
  197.                     </td>    
  198.     
  199.                     <td>    
  200.                         <input    
  201.                             type="text"    
  202.                             name="EJR"    
  203.                             value={this.state.rows[idx].EJR}    
  204.                             onChange={this.handleChange(idx)}    
  205.                             id={this.state.rows[idx].id}    
  206.                         />    
  207.                     </td>    
  208.                 </tr>    
  209.             );    
  210.     
  211.         });    
  212.     
  213.         return (    
  214.             <div className="container mainDynTable">    
  215.                 <table id="dynVPITable">    
  216.                     <thead>    
  217.                         <tr>    
  218.                             {    
  219.     
  220.                                 this.state.tableHeaders.map(function (headerText) {    
  221.     
  222.                                     return <th> {headerText} </th>    
  223.     
  224.                                 })    
  225.     
  226.                             }    
  227.     
  228.                         </tr>    
  229.     
  230.                     </thead>    
  231.     
  232.                     <tbody>    
  233.     
  234.                         {list}    
  235.     
  236.                     </tbody>    
  237.     
  238.                 </table>    
  239.     
  240.                 <div className="main-width add-remove-icons">    
  241.     
  242.                     <span id="add-row" onClick={this.handleAddRow}     
  243.     
  244. className="document-icons-area">    
  245.     
  246. <Icon iconName="CalculatorAddition" className="ms-IconExample" /></span>    
  247.     
  248.                     <span id="delete-row" onClick={this.handleRemoveRow}     
  249.     
  250. className="document-icons-area">    
  251.     
  252. <Icon iconName="CalculatorSubtract" className="ms-IconExample" /></span>    
  253.     
  254.                 </div>    
  255.     
  256.             </div>    
  257.     
  258.         );    
  259.     
  260.     }    
  261.  }    
Integrate "ReactTable.tsx" child component with your "Parent" webpart.
 
Declare your state variable as shown below. 
  1. export interface ISpFxRichTextEditorState {  
  2.   ReactTableResult:any;  
  3. }  
Declare the tableData property in your props.ts file, as shown below.
  1. export interface ISpFxRichTextEditorProps {   
  2. tableData?:any;       
  3. renderTable?:any;     
  4. }     
Import ReactTable.
  1. import * as React from 'react';      
  2. import ReactTable from './ReactTable/ReactTable';     
Declare your state variable in your parent component. 
  1. constructor(props:  ISpFxRichTextEditorProps, state: ISpFxRichTextEditorState) {  
  2.     super(props);  
  3.     this.state = {  
  4.          ReactTableResult:[]  
  5.     };  
  6. }  
Handle callback event to capture events from child to parent callback and store in your state variable as shown below
  1. handleTableData = (tableRowColl) => {  
  2.     this.setState({ ReactTableResult: tableRowColl });  
  3. }  
Add your component in your Render function as shown below.
  1. <ReactTable tableData={this.handleTableData} renderTable={this.state.ReactTableResult} ></ReactTable>   
Note
The values will be stored as JSON array. You can store the values in your SharePoint list (Multiple lines of text (plain text)) and retrieve the same.
 
Install SpFx helper node module and Import SPFXHelper as shown below
  1. import {SPListOperations}  from 'spfxhelper';  
Define SPListOperations.
  1. /*Define SPListOperation to perform CRUD Operations*/  
  2.   private get oListOperation(): SPListOperations {  
  3.     return SPListOperations.getInstance(this.state.HTTPClient as SPHttpClient, this.state.SPSiteURL);  
  4.   }  
Add React Table values to SharePoint List.
  1. /* Add the items to the list */  
  2.   private AddItems(workordertype: string): void {  
  3.     try {  
  4.         var dynamicTableData='';  
  5. var workOrderList='Your List Name';
  6.         var resTable = this.ValidateDynamicTable(this.state.ReactTableResult);  
  7.         if(resTable.length > 0){  
  8.           dynamicTableData = JSON.stringify(resTable);  
  9.         }  
  10.                  
  11.         const bodyforadding: string = JSON.stringify({  
  12.           __metadata: { 'type'"SP.Data.SharePointListItem" },  
  13.           TableContent: dynamicTableData            
  14.         });  
  15.           
  16.         /*Add items to sharepoint list */  
  17.         this.AddListItems(this.props.ctx.spHttpClient, this.props.siteUrl, workOrderList, bodyforadding);  
  18.         
  19.     } catch (error) {  
  20.       console.log("Error in addItems : " + error);  
  21.     }  
  22.   }  
  23.     
  24.   /*Validate Dynamic table */  
  25.     public ValidateDynamicTable(vpiReactTableResult){  
  26.         try {  
  27.             var stateReactArr;  
  28.             if (vpiReactTableResult == "" || vpiReactTableResult == null) {  
  29.                 stateReactArr = [];  
  30.             } else {  
  31.                 if (vpiReactTableResult.length > 0) {  
  32.                     stateReactArr = vpiReactTableResult;  
  33.                 } else {  
  34.                     stateReactArr = [];  
  35.                 }  
  36.             }  
  37.             return stateReactArr;  
  38.         } catch (error) {  
  39.             console.log("Error in ValidateDynamicTable : " + error);  
  40.         }  
  41.     }  
  42.       
  43.     /** Add item to sharepoint list */  
  44.     public AddListItems(spHttpClientVPI,siteURL,sharepointList,body):void{  
  45.         try{  
  46.             this.spHttpClient.post(this.siteUrl + "/_api/web/lists/getbytitle('" + sharepointList + "')/items",  
  47.                 SPHttpClient.configurations.v1,  
  48.                 {  
  49.                     headers: {  
  50.                         'Accept''application/json;odata=verbose',  
  51.                         'Content-type''application/json;odata=verbose',  
  52.                         'odata-version'''  
  53.                     },  
  54.                     body: body  
  55.                 }).then(  
  56.                     (): void => {  
  57.                         alert("Item Added Successfully!!!");  
  58.                     }  
  59.                 );  
  60.         }catch(error){  
  61.             console.log("Error in AddListItems : " + error);  
  62.         }  
  63.     }  
Retrieve React Table values from SharePoint List.
  1. /*Get item from sharepoint list */  
  2.   private async GetItem(itemID: string) {  
  3.     try {  
  4. var workOrderList = '<your sharepoint list name>';
  5.       var redirectionEmailURL = this.props.siteUrl + "/_api/web/lists/getbytitle('" + workOrderList + "')/Items?$filter=Id eq '" + itemID + ";  
  6.       const responseEmail = await this.props.ctx.spHttpClient.get(redirectionEmailURL, SPHttpClient.configurations.v1);  
  7.       const responseEmailJSON = await responseEmail.json();  
  8.         
  9.       this.oListOperation.getListItemByID(workOrderList, itemID)  
  10.             .then(async value => {  
  11.               var responseData = $.parseJSON(value.responseJSON);  
  12.               var responseJSON = responseData.result;                  
  13.               this.setState({ ReactTableResult: responseJSON.TableContent == "" ? "" : JSON.parse(responseJSON.TableContent) });                 
  14.             });                  
  15.     } catch (error) {  
  16.       console.log("Error in GetItem : " + error);  
  17.     }    
  18.   }  
CSS
  1. /*Dynamic Table*/  
  2. #dynVPITable {  
  3.     border-collapsecollapse;  
  4.     width100%;  
  5.     margin-bottom10px;  
  6.   }  
  7.     
  8.   #dynVPITable td, #dynVPITable th {  
  9.     border1px solid #ddd;  
  10.     padding8px;  
  11.   }  
  12.     
  13.   #dynVPITable th {  
  14.     padding-top12px;  
  15.     padding-bottom12px;  
  16.     background-color#f1f1f1;  
  17.       
  18.   }  
  19.     
  20.   #dynVPITable input {  
  21.       border1px solid #596259;  
  22.       width100%;  
  23.       height32px;  
  24.   }  
  25.   
  26.   .mainDynTable{  
  27.    width:100%;  
  28.   }  
  29.   
  30.   .divDynamicTable{  
  31.    width:100%;  
  32.   }    
Button CSS
  1. /*Dynamic table Add and Remove */  
  2.   
  3. .add-remove-icons .document-icons-area {  
  4.     background#f1f1f1;  
  5.     border1px solid #ddd;  
  6.     padding7px;  
  7.     border-radius: 5px;  
  8.     cursorpointer;  
  9.     color#000;  
  10. }  
  11. .add-remove-icons {  
  12.     floatright;  
  13.     widthauto;  
  14. }  
Output
 

Please feel free to share your comments.
 
Hope this helps !!!!!