Noah R.
Noah R.

Reputation: 3

React - Array Splice Trouble

Learning React. Attempting to make my own mini-app based very closely on what's done here: https://www.youtube.com/watch?v=-AbaV3nrw6E.

I'm having a problem with the deletion of comments in my app. I've looked in several other places for people having similar errors, but it seems the problem is within my own code (and yet I can find no errors). I've scoured the Babel file over and over, but to no avail.

Here are the specifics: When you create a new comment, you have two options in the form of buttons: Save and Delete. After exactly one comment is written and you press "Save," the delete function works just fine. However, if there are three comments total (for example) and you click "delete" on the first one, the next comment (the second, in this case) is deleted.

Hopefully that makes some amount of sense.

Can you find my error? The math/logic behind the delete function is located on line 71 under the name "deleteComment."

Full Pen here.

var CommentSection = React.createClass({

    getInitialState: function() {
        return {editing: true}
    },

    edit: function() {
        this.setState({editing: true});
    },

    save: function() {
        this.props.updateCommentText(this.refs.newText.value, this.props.index);
        this.setState({editing: false});
    },

    delete: function() {
        this.props.deleteFromCard(this.props.index);
    },

    renderNormal: function() {
        return (
            <div className="comment-section">
                <div className="comment-content">{this.props.children}</div>
                <a className="-edit" onClick={this.edit}>Edit</a>
            </div>
        );
    },

    renderEdit: function() {
        return (
            <div className="comment-section">
                <textarea ref="newText" defaultValue={this.props.children}></textarea>
                <button className="-save" onClick={this.save}>Save</button>
                <button className="-delete" onClick={this.delete}>Delete</button>
            </div>
        );
    },

    render: function() {
        if(this.state.editing) {
            return this.renderEdit();
        } else {
            return this.renderNormal();
        }
    }

});

var PhotoSection = React.createClass({

    render: function() {
        return <div className="photo-section"></div>;
    }

});

var Desk = React.createClass({

    getInitialState: function() {
        return {
            comments: []
        }
    },

    addComment: function(text) {
        var arr = this.state.comments;
        arr.push(text);
        this.setState({comments: arr})
    },

    deleteComment: function(i) {
        console.log(i);
        var arr = this.state.comments;
        arr.splice(i, 1);
        this.setState({comments: arr})
    },

    updateComment: function(newText, i) {
        var arr = this.state.comments;
        arr[i] = newText;
        this.setState({comments: arr})
    },

    commentFormat: function(text, i) {
        return (
            <CommentSection key={i} index={i} updateCommentText={this.updateComment} deleteFromCard={this.deleteComment}>
                {text}
            </CommentSection>
        );
    },

    render: function() {
        return (
            <div className="desk">
                <div className="card">
                    <PhotoSection />
                    <div className="comment-section-backing">
                        <button className="-comment" onClick={this.addComment.bind(null, "")}>Leave a Comment</button>
                        {this.state.comments.map(this.commentFormat)}
                    </div>
                </div>
            </div>
        );
    }

});

ReactDOM.render(<Desk />, document.getElementById('app'));

Upvotes: 0

Views: 2182

Answers (3)

dting
dting

Reputation: 39287

Your problem stems from using the index as keys:

https://facebook.github.io/react/docs/lists-and-keys.html#keys

When you delete an item from your array, the array in the state is correctly updated. However, when the array is rendered, they keys will all be the same regardless of which element you deleted except there will be one less.

At this point the reconciliation happens and your components are rerendered. However you have an (uncontrolled) textarea in each component that holds its own internal state. The uncontrolled textarea component does get it's default (initial) value from the children prop but is otherwise unaffected by changes to that value. Therefore the re-rendering of the components with new values for text do not change the values in those textarea instances.

If the keys for the components in the mapped components were not linked to the index, the correct component would be removed.

edit: The code in the pen has changed slightly where there are two different render branches (editing, normal). Since the normal rendering doesn't use the uncontrolled textarea inputs the pen no longer exhibits the aberrant behavior.

Upvotes: 1

Mark Williams
Mark Williams

Reputation: 2308

There was an issue with using this.props.children when rendering the CommentSection component

Changing the code to use a prop:

    return (
      <div className="comment-section">
        <div className="comment-content">{this.props.commentText}</div>
        <a className="-edit" onClick={this.edit}>Edit</a>
        <button className="-delete" onClick={this.delete}>Delete</button>
      </div>
   );

and setting this in the commentFormat functiion in the container:

commentFormat: function(text, i) {
  return (
    <CommentSection 
      key={i} 
      index={i} 
      updateCommentText={this.updateComment}       
      deleteFromCard={this.deleteComment} 
      commentText={text}>
    </CommentSection>
        );
}

appears to work.

CodePen

Upvotes: 0

Seb Bizeul
Seb Bizeul

Reputation: 1006

Try with Array.filter.

deleteComment: function(i) {
    var arr = this.state.comments.filter(function(comment) {
        return comment.index !== i;
    });
    this.setState({comments: arr});
},

Upvotes: 0

Related Questions