3

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

1

1 Answer 1

6

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/

Sign up to request clarification or add additional context in comments.

1 Comment

I've spent an hour or so to find out why my new empty row that gets added has incorrect data inside. Data in the this.state wasn't reflected by the DOM. Now I know why. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.