Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.js table: remove row button visually removes incorrect row

Tags:

reactjs

I am using React.js to dynamically create a html table containing text boxes. I have rows that can be removed by a button click. I expect, when I click "remove" on the first row that the table re-renders with row 1 removed. However, when react re-draws the table, it looks like it always removes the last row of the table from the DOM instead of using the actual values from my state object. Perhaps I found a bug? Here's my code:

/** @jsx React.DOM */

var MyApp = React.createClass({
    getInitialState: function () {
      return {
        col_one: ['c1r1', 'c1r2', 'c1r3'],
        col_two: ['c2r1', 'c1r2', 'c1r3'],
        col_three: ['c3r1', 'c3r2', 'c3r3']
      }
    },
    handleCellChange: function (colName, index, e) {
      console.log('onChange:', colName, index, e.target.value);
    },
    handleRemove: function (i) {
      var that = this;
      console.log('removing row:',i);
      _.forEach(this.state, function (val, colName) {
        that.state[colName].splice(i,1); // BUG???
        //_.pullAt(that.state[key], i); // doesn't work either
      });
      console.log(this.state);
      this.setState(this.state);

    },
    render: function() {
      var that = this,
        rows = [],
        cols = _.keys(this.state);

      rows.push(
      <tr>
      {cols.map(function (col) {
        return (
          <th>{col}</th>
        )
      })}
      </tr>
      )
      for (var i = 0; i < this.state[cols[0]].length; i++) {
        rows.push(
          <tr>
            {cols.map(function (col) {
              return (
                <td>
                  <input type="text" defaultValue={that.state[col][i]} onChange={that.handleCellChange.bind(that, col, i)} />
                </td>
              )
            })}
            <td>
              <button onClick={this.handleRemove.bind(this, i)}>Remove</button>
            </td>
          </tr>
        )
      }

      return (
        <table>
          <tbody>
            {rows}
          </tbody>
        </table>
      );
    }
});

React.renderComponent(<MyApp />, document.body);

seen here as a JSBin

like image 916
Chris Montgomery Avatar asked Jun 07 '26 23:06

Chris Montgomery


1 Answers

You're using defaultValue - that makes the input an uncontrolled component, which gets an initial value set but whose displayed value is never touched again by React unless you blow the whole component away (e.g. with a key change on it or an ancestor) and it has to recreate it from scratch.

That's why you don't see the new defaultValue being displayed - because there are no unique keys on each row, React finds there's one fewer row on re-render and removes the last row from the real DOM, but any new defaultValue for inputs in a retained row has no effect..

Here's the same code but using value, which makes it a controlled component, which reflects the new value it gets on re-render:

http://jsbin.com/hacitidaqe/1/

like image 135
Jonny Buchanan Avatar answered Jun 10 '26 18:06

Jonny Buchanan