wuno
wuno

Reputation: 9865

Changing State In React

Background

I am having a problem in my react app I am writing. I have been trying to remove a row from a table but I keep getting

setState is undefined

So basically this is how I have this setup.

This function will remove the row from the table. I have not tested this because I keep getting the

error setState of undefined

.

function removeRow(id) {
  this.setState(state => {
     state.tableData.splice(id, 1);
     return {tableData: state.tableData};
   });
}

Question

How do I correctly remove the tr from the table when the onClick method fires of so that the state will change and render the table again without the tr that was removed?

Examples

This is the onClick method which is calling the removeRow().

<td><a href="#" onClick={() => { removeRow(tableData[key].id) }}>Delete</a></td>

This is what it all looks like together,

var requests =  [   
        {"id":1, "title":"Request from Nancy","updated_at":"2015-08-15 12:27:01 -0600","created_at":"2015-08-12 08:27:01 -0600","status":"Denied"}
    ]

class TableDisplay extends React.Component {
constructor() {
    super();
    this.state = {
      tableData: requests
    }
  }

  render() {
    const {tableData} = this.props;

    return (
      <div className="table-wrapper">
        <div><table className="table">
            <tr className="separate"><td>Title</td><td>Status</td>           
               <td>Created</td><td>Updated</td><td>Delete</td></tr>
     {Object.keys(tableData).map(function(key) {       

     function removeRow(id) {
         this.setState(state => {
            state.tableData.splice(id, 1);
            return {tableData: state.tableData};
        });
     }

        return // removed extra code to make it easy to read.
        <td><a href="#" onClick={() => { removeRow(tableData[key].id) }}>Delete</a></td>
        </tr>;

        })}
           </table>
        </div>
      </div>
    );
  }
}

I also tried to move the removeRow() directly under the constructor and above the render() but when I do that then removeRow() throws an error when the onClick fires,

removeRow is not defined.

class TableDisplay extends React.Component {
constructor() {
    super();
    this.state = {
      tableData: requests
    }
  }

// I have to remove "function" from the declaration or the app wont render from parse error.

   removeRow(id) {
         this.setState(state => {
            state.tableData.splice(id, 1);
            return {tableData: state.tableData};
        });
     }

  render() {
    const {tableData} = this.props;

    return (
      <div className="table-wrapper">
        <div><table className="table">
            <tr className="separate"><td>Title</td><td>Status</td>           
               <td>Created</td><td>Updated</td><td>Delete</td></tr>
     {Object.keys(tableData).map(function(key) {       

        return // removed extra code to make it easy to read.
        <td><a href="#" onClick={() => { removeRow(tableData[key].id) }}>Delete</a></td>
        </tr>;

        })}
           </table>
        </div>
      </div>
    );
  }
}

Upvotes: 2

Views: 15149

Answers (4)

Mani Varshney
Mani Varshney

Reputation: 11

**Put removeRow method above render() and give current scope reference as var that = this; **


     class TableDisplay extends React.Component {
      constructor() {
    super();
    this.state = {
      selectValue: 'all',
      tableData: requests
    }
    }

    removeRow() {
             this.setState(state => {
             state.tableData.splice(id, 1);
             return {tableData: state.tableData};
        });
     }

    render() {
    const {selectValue} = this.props;
    const {tableData} = this.props;
    var that = this;
     return (
      <div className="table-wrapper">
        <div><table className="table">
              <tr className="seperate"><td>Title</td><td>Status</td><td>Created</td><td>Updated</td><td>Delete</td></tr>
            {Object.keys(tableData).map(function(key) {  
        let styling = "bg-plain";
                let hidden = "hidden";
        let tds = document.querySelectorAll(".table td");
        let trs = document.querySelectorAll(".table tr");

        function showHiddenRows() {
        for (var i = 0; i < trs.length; i++) {
                trs[i].classList.remove("hidden");
            }
        }

        function setStyle() {
        if (tableData[key].status === "Approved") {
                    styling = "bg-success";
                    } else if (tableData[key].status === "Denied") {
                    styling = "bg-danger";
                }   
            for (var i = 0; i < tds.length; i++) {
                        if (tds[i].innerText === "Approved") {
                  tds[i].parentNode.setAttribute('class', 'bg-success')
                    } else if (tds[i].innerText === "Denied") {
                                    tds[i].parentNode.setAttribute('class', 'bg-danger')
                    }
            }
        }

        function filterTable(fa, fb) {
        for (var i = 0; i < tds.length; i++) {
                        if (tds[i].innerText === fa) {
                  tds[i].parentNode.setAttribute('class', 'hidden')
                    } else if (tds[i].innerText === fb) {
                                    tds[i].parentNode.setAttribute('class', 'hidden')
                    }
           }
        }



        if (selectValue === "all") {
                    showHiddenRows();
            setStyle(); 
        } else if (selectValue === "approved") {
             showHiddenRows();
           setStyle();      
           filterTable("Pending", "Denied");
            } else if (selectValue === "pending") {
            showHiddenRows();
            setStyle(); 
            filterTable("Approved", "Denied");
            } else if (selectValue === "denied") {
          showHiddenRows();
          setStyle();
          filterTable("Approved", "Pending");
        }

      {console.log(tableData[key].id)} 
        return <tr key={tableData[key].id} id={tableData[key].id} className={styling}>
        <td>{tableData[key].title}</td>
        <td>{tableData[key].status}</td>
        <td>{tableData[key].created_at.slice(0, 10)}</td>
        <td>{tableData[key].updated_at.slice(0, 10)}</td>
        <td><a href="#" onClick={() => { that.removeRow() }}>Delete</a></td>
        </tr>;

        })}
              </table>
            </div>
      </div>
    );
  }
}

Upvotes: 1

Shubham Khatri
Shubham Khatri

Reputation: 281626

When you call removeRow in the second solution, you need to call it like this.removeRow since it is outside the render function. Also you need to bind it so that you can access setState. For this purpose you can use the arrow function

class TableDisplay extends React.Component {
constructor() {
    super();
    this.state = {
      tableData: requests
    }
  }

// I have to remove "function" from the declaration or the app wont render from parse error.

   removeRow = (id) => {
         this.setState(state => {
            state.tableData.splice(id, 1);
            return {tableData: state.tableData};
        });
     }

  render() {
    const {tableData} = this.props;

    return (
      <div className="table-wrapper">
        <div><table className="table">
            <tr className="separate"><td>Title</td><td>Status</td>           
               <td>Created</td><td>Updated</td><td>Delete</td></tr>
     {Object.keys(tableData).map(function(key) {       

        return // removed extra code to make it easy to read.
        <td><a href="#" onClick={() => { this.removeRow(tableData[key].id) }}>Delete</a></td>
        </tr>;

        }.bind(this))}
           </table>
        </div>
      </div>
    );
  }
}

Upvotes: 1

StateLess
StateLess

Reputation: 5402

The context of this is lost.

You can bind function to this.

class TableDisplay extends React.Component {
constructor() {
    super();
    this.state = {
      tableData: requests
    }
  //ADD THIS LINE
  this.removeRow = this.removeRow.bind(this);
  }

// I have to remove "function" from the declaration or the app wont render from parse error.

   removeRow(id) {
         this.setState(state => {
            state.tableData.splice(id, 1);
            return {tableData: state.tableData};
        });
     }
 ......

<a href="#" onClick={() => { this.removeRow(tableData[key].id) }}

Or alternatively if you are using ES6 Stage 2 preset, you can use arrow functions

class TableDisplay extends React.Component {
constructor() {
    super();
    this.state = {
      tableData: requests
    }
  }

// I have to remove "function" from the declaration or the app wont render from parse error.

   removeRow = (id) => {
         this.setState(state => {
            state.tableData.splice(id, 1);
            return {tableData: state.tableData};
        });
     }
 ......

Upvotes: 2

Max Sindwani
Max Sindwani

Reputation: 1267

There are a few issues with your component, namely the fact that you don't bind the context in the remove function (hence setState is undefined). Try something like this:

const TableRow = (props) => (
   <tr>
      <td>{props.title}</td>
      <td>{props.status}</td>
      <td>{props.updated_at}</td>
      <td>{props.created_at}</td>
      <td><a href="#" onClick={() => props.remove(props.index)}>Delete</a></td>
   </tr>
)

class TableDisplay extends React.Component {

   constructor() {
      super();
      this.state = {
         tableData: [{
            "id":1,
            "title":"Request from Nancy",
            "updated_at":"2015-08-15 12:27:01 -0600",
            "created_at":"2015-08-12 08:27:01 -0600",
            "status":"Denied"
         }]
      };
   }

   remove(index) {
      this.setState({
         tableData: this.state.tableData.filter((row, i) => i !== index)
      });
   }

   render() {
      return (
         <div className="table-wrapper">
            <div>
               <table className="table">
                  <tbody>
                     {
                        this.state.tableData.map((row, i) => {
                           return (
                              <TableRow
                                 title={row.title}
                                 id={row.id}
                                 updated_at={row.updated_at}
                                 created_at={row.created_at}
                                 status={row.status}
                                 remove={this.remove.bind(this)}
                                 index={i}
                                 key={i} />
                           );
                        })
                     }
                  </tbody>
               </table>
            </div>
         </div>
      );
   }
}

ReactDOM.render((
   <TableDisplay />
), document.getElementById("app"));

Upvotes: 3

Related Questions