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.
- /* import react and office ui fabric */
- import * as React from 'react';
- import { IHelloWorldProps } from '../IHelloWorldProps';
- import { DefaultButton, PrimaryButton, DialogType, Dialog, DialogFooter,Icon,Label, TextField, ActionButton,autobind } from 'office-ui-fabric-react';
- /* Initial loading of table column properties */
- const tableColProps=[{
- id:1,
- NounName:"",
- CurrentPN:"",
- NewPart:"",
- Option:"",
- Supplier:"",
- EJR:""
- }]
- /* Declare React Table State variables */
- export interface IReactTableState {
- tableHeaders:any;
- rows:any;
- rowLimit:number;
- addButtonText:string;
- removeButtonText:string;
- }
- export default class ReactTable extends React.Component<IHelloWorldProps,IReactTableState> {
- /* React Table constructor */
- constructor(props: IHelloWorldProps) {
- super(props);
- this.state = {
- tableHeaders:["Noun Name", "Current PN", "New Part", "Option#", "Supplier", "EJR"],
- rows:this.props.renderTable,
- rowLimit:20,
- addButtonText :"Add New Row",
- removeButtonText : "Delete Row"
- };
- }
- /* This event will fire on cell change */
- handleChange = (index) => evt => {
- try {
- var item = {
- id: evt.target.id,
- name: evt.target.name,
- value: evt.target.value
- };
- var rowsArray = this.state.rows;
- var newRow = rowsArray.map((row, i) => {
- for (var key in row) {
- if (key == item.name && row.id == item.id) {
- row[key] = item.value;
- }
- }
- return row;
- });
- this.setState({ rows: newRow });
- this.props.tableData(rowsArray);
- } catch (error) {
- console.log("Error in React Table handle change : " + error);
- }
- };
- /* This event will fire on adding new row */
- handleAddRow = () => {
- try {
- var id = (+ new Date() + Math.floor(Math.random() * 999999)).toString(36);
- const tableColProps = {
- id: id,
- NounName: "",
- CurrentPN: "",
- NewPart: "",
- Option: "",
- Supplier: "",
- EJR: ""
- }
- if (this.state.rows.length < this.state.rowLimit) {
- this.state.rows.push(tableColProps);
- this.setState(this.state.rows);
- } else {
- alert('Add row limit exceeds');
- }
- } catch (error) {
- console.log("Error in React Table handle Add Row : " + error)
- }
- };
- /* This event will fire on remove row */
- handleRemoveRow = () => {
- try {
- var rowsArray = this.state.rows;
- if (rowsArray.length > 1) {
- var newRow = rowsArray.slice(0, -1);
- this.setState({ rows: newRow });
- }
- this.props.tableData(newRow);
- } catch (error) {
- console.log("Error in React Table handle Remove Row : " + error);
- }
- };
- /* This event will fire on remove specific row */
- handleRemoveSpecificRow = (idx) => () => {
- try {
- const rows = [this.state.rows]
- if(rows.length > 1){
- rows.splice(idx, 1);
- }
- this.setState({ rows });
- } catch (error) {
- console.log("Error in React Table handle Remove Specific Row : " + error);
- }
- }
- /* This event will fire on next properties update */
- componentWillReceiveProps(nextProps) {
- try{
- if (nextProps.renderTable.length > 0) {
- this.setState({ rows: nextProps.renderTable });
- }else{
- this.setState({ rows: tableColProps });
- }
- }catch(error){
- console.log("Error in React Table component will receive props : " + error);
- }
- }
- render() {
- let list = this.state.rows.map((item,idx) =>{
- return (
- <tr key={idx}>
- <td>
- <input
- type="text"
- name="NounName"
- value={this.state.rows[idx].NounName}
- onChange={this.handleChange(idx)}
- id={this.state.rows[idx].id}
- />
- </td>
- <td>
- <input
- type="text"
- name="CurrentPN"
- value={this.state.rows[idx].CurrentPN}
- onChange={this.handleChange(idx)}
- id={this.state.rows[idx].id}
- />
- </td>
- <td>
- <input
- type="text"
- name="NewPart"
- value={this.state.rows[idx].NewPart}
- onChange={this.handleChange(idx)}
- id={this.state.rows[idx].id}
- />
- </td>
- <td>
- <input
- type="text"
- name="Option"
- value={this.state.rows[idx].Option}
- onChange={this.handleChange(idx)}
- id={this.state.rows[idx].id}
- />
- </td>
- <td>
- <input
- type="text"
- name="Supplier"
- value={this.state.rows[idx].Supplier}
- onChange={this.handleChange(idx)}
- id={this.state.rows[idx].id}
- />
- </td>
- <td>
- <input
- type="text"
- name="EJR"
- value={this.state.rows[idx].EJR}
- onChange={this.handleChange(idx)}
- id={this.state.rows[idx].id}
- />
- </td>
- </tr>
- );
- });
- return (
- <div className="container mainDynTable">
- <table id="dynVPITable">
- <thead>
- <tr>
- {
- this.state.tableHeaders.map(function (headerText) {
- return <th> {headerText} </th>
- })
- }
- </tr>
- </thead>
- <tbody>
- {list}
- </tbody>
- </table>
- <div className="main-width add-remove-icons">
- <span id="add-row" onClick={this.handleAddRow}
- className="document-icons-area">
- <Icon iconName="CalculatorAddition" className="ms-IconExample" /></span>
- <span id="delete-row" onClick={this.handleRemoveRow}
- className="document-icons-area">
- <Icon iconName="CalculatorSubtract" className="ms-IconExample" /></span>
- </div>
- </div>
- );
- }
- }
Integrate "ReactTable.tsx" child component with your "Parent" webpart.
Declare your state variable as shown below.
- export interface ISpFxRichTextEditorState {
- ReactTableResult:any;
- }
Declare the tableData property in your props.ts file, as shown below.
- export interface ISpFxRichTextEditorProps {
- tableData?:any;
- renderTable?:any;
- }
Import ReactTable.
- import * as React from 'react';
- import ReactTable from './ReactTable/ReactTable';
Declare your state variable in your parent component.
- constructor(props: ISpFxRichTextEditorProps, state: ISpFxRichTextEditorState) {
- super(props);
- this.state = {
- ReactTableResult:[]
- };
- }
Handle callback event to capture events from child to parent callback and store in your state variable as shown below
- handleTableData = (tableRowColl) => {
- this.setState({ ReactTableResult: tableRowColl });
- }
Add your component in your Render function as shown below.
- <ReactTable tableData={this.handleTableData} renderTable={this.state.ReactTableResult} ></ReactTable>
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
- import {SPListOperations} from 'spfxhelper';
Define SPListOperations.
- /*Define SPListOperation to perform CRUD Operations*/
- private get oListOperation(): SPListOperations {
- return SPListOperations.getInstance(this.state.HTTPClient as SPHttpClient, this.state.SPSiteURL);
- }
Add React Table values to SharePoint List.
- /* Add the items to the list */
- private AddItems(workordertype: string): void {
- try {
- var dynamicTableData='';
- var workOrderList='Your List Name';
- var resTable = this.ValidateDynamicTable(this.state.ReactTableResult);
- if(resTable.length > 0){
- dynamicTableData = JSON.stringify(resTable);
- }
- const bodyforadding: string = JSON.stringify({
- __metadata: { 'type': "SP.Data.SharePointListItem" },
- TableContent: dynamicTableData
- });
- /*Add items to sharepoint list */
- this.AddListItems(this.props.ctx.spHttpClient, this.props.siteUrl, workOrderList, bodyforadding);
- } catch (error) {
- console.log("Error in addItems : " + error);
- }
- }
- /*Validate Dynamic table */
- public ValidateDynamicTable(vpiReactTableResult){
- try {
- var stateReactArr;
- if (vpiReactTableResult == "" || vpiReactTableResult == null) {
- stateReactArr = [];
- } else {
- if (vpiReactTableResult.length > 0) {
- stateReactArr = vpiReactTableResult;
- } else {
- stateReactArr = [];
- }
- }
- return stateReactArr;
- } catch (error) {
- console.log("Error in ValidateDynamicTable : " + error);
- }
- }
- /** Add item to sharepoint list */
- public AddListItems(spHttpClientVPI,siteURL,sharepointList,body):void{
- try{
- this.spHttpClient.post(this.siteUrl + "/_api/web/lists/getbytitle('" + sharepointList + "')/items",
- SPHttpClient.configurations.v1,
- {
- headers: {
- 'Accept': 'application/json;odata=verbose',
- 'Content-type': 'application/json;odata=verbose',
- 'odata-version': ''
- },
- body: body
- }).then(
- (): void => {
- alert("Item Added Successfully!!!");
- }
- );
- }catch(error){
- console.log("Error in AddListItems : " + error);
- }
- }
Retrieve React Table values from SharePoint List.
- /*Get item from sharepoint list */
- private async GetItem(itemID: string) {
- try {
- var workOrderList = '<your sharepoint list name>';
- var redirectionEmailURL = this.props.siteUrl + "/_api/web/lists/getbytitle('" + workOrderList + "')/Items?$filter=Id eq '" + itemID + ";
- const responseEmail = await this.props.ctx.spHttpClient.get(redirectionEmailURL, SPHttpClient.configurations.v1);
- const responseEmailJSON = await responseEmail.json();
- this.oListOperation.getListItemByID(workOrderList, itemID)
- .then(async value => {
- var responseData = $.parseJSON(value.responseJSON);
- var responseJSON = responseData.result;
- this.setState({ ReactTableResult: responseJSON.TableContent == "" ? "" : JSON.parse(responseJSON.TableContent) });
- });
- } catch (error) {
- console.log("Error in GetItem : " + error);
- }
- }
CSS
- /*Dynamic Table*/
- #dynVPITable {
- border-collapse: collapse;
- width: 100%;
- margin-bottom: 10px;
- }
- #dynVPITable td, #dynVPITable th {
- border: 1px solid #ddd;
- padding: 8px;
- }
- #dynVPITable th {
- padding-top: 12px;
- padding-bottom: 12px;
- background-color: #f1f1f1;
- }
- #dynVPITable input {
- border: 1px solid #596259;
- width: 100%;
- height: 32px;
- }
- .mainDynTable{
- width:100%;
- }
- .divDynamicTable{
- width:100%;
- }
Button CSS
- /*Dynamic table Add and Remove */
- .add-remove-icons .document-icons-area {
- background: #f1f1f1;
- border: 1px solid #ddd;
- padding: 7px;
- border-radius: 5px;
- cursor: pointer;
- color: #000;
- }
- .add-remove-icons {
- float: right;
- width: auto;
- }
Output

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

Krati AgrawalPosted Dec 23, 2021, 12:47 PM
Can you please what is in IHelloWorldProps?
Mangesh KhatalPosted Oct 30, 2021, 7:30 PM
Could you please share source code
A APosted Nov 16, 2020, 12:45 AM
HI Madu at line var responseData = $.parseJSON(value.responseJSON); i am getting error - property response json does not exist on type ilistitemresponse
Sankararao JilledimudiPosted May 28, 2020, 12:01 PM
Hi Madhu, nice article I followed working everything as expected, but in edit mode the table is in disabled mode. How can enable in edit mode? Appreciate your help on this.
Andrey LitvinovPosted Feb 21, 2020, 2:28 PM
Hi Madhukumar, could you please share your colmplete sorce code, e.g. to github?
Akash SonarePosted Nov 8, 2019, 4:35 AM
HI Madhu Kumar, it is a nice article but i am trying to remove specific row and applied logic for it also it is removing all rows except first row
Jai ChowdaryPosted Oct 22, 2019, 11:53 PM
Hi Madhu kumar I want help from your end.I have dought in this blog.may i know how to declare the constructor in base client side webpart.constructor(props: ISpFxRichTextEditorProps, state: ISpFxRichTextEditorState) { super(props); this.state = { ReactTableResult:[] }; }