0

I think I'm having a fundamental issue understanding state and props... I have an array of records, and when I console.log the array it returns the correct records, but .setState is not changing the returned records in the UI. I wonder if perhaps handleCommentDelete needs to be moved to the parent component? Here's the parent:

import React from 'react';
import { render } from 'react-dom';

import CommentForm from './CommentForm';
import CommentList from './CommentList';


class CommentBox extends React.Component {
    constructor(props) {
        super(props);
        this.state = { data: [] };
        this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
    }
    componentDidMount() {
        this.loadCommentsFromServer();
        window.setInterval(
            () => this.loadCommentsFromServer(),
            this.props.pollInterval,
        );
    }
    handleCommentSubmit(comment) {
        const comments = this.state.data;
        comment.Id = comments.length + 1;
        const newComments = comments.concat([comment]);
        this.setState({ data: newComments });

        const data = new FormData();
        data.append('Id', comment.Id);
        data.append('Name', comment.Name);
        data.append('Phone', comment.Phone);
        data.append('Email', comment.Email);

        const xhr = new XMLHttpRequest();
        xhr.open('post', this.props.submitUrl, true);
        xhr.onload = () => this.loadCommentsFromServer();
        xhr.send(data);
    }
    loadCommentsFromServer() {
        const xhr = new XMLHttpRequest();
        xhr.open('get', this.props.url, true);
        xhr.onload = () => {
            const data = JSON.parse(xhr.responseText);
            this.setState({ data: data });
        };
        xhr.send();
    }
    render() {
        return (
            <div className="row padBottom_10">
                <div className="col-md-12">
                    <CommentForm onCommentSubmit={this.handleCommentSubmit} />
                </div>
                <div className="col-md-12">
                    <div className="commentBox border borderBorderGrey highlightPrimaryGrey">
                        <CommentList data={this.state.data} />
                    </div>
                </div>
            </div>

        );
    }
}

export default CommentBox;

...and here's the child component, where I'm trying to delete a record.

import React from 'react';
import { render } from 'react-dom';

class CommentList extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            data: [{}]
        };
        this.handleCommentDelete = this.handleCommentDelete.bind(this);
    }
    handleCommentDelete(id) {
        console.log(this.props.data); // returns n records
        let comments = [...this.props.data];
        this.setState({
            data: comments.filter(comment => comment.Id !== id)
        });
        console.log(comments.filter(comment => comment.Id !== id)); // returns n - 1 records
    }
    render() {
        return (
            <table className="commentList react-bs-table">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Phone</th>
                        <th>Email</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    {
                        this.props.data.map((comment, i) => {
                            return (
                                <tr className="comment" key={i}>
                                    <td className="commentId">{comment.Id}</td>
                                    <td className="commentName">{comment.Name}</td>
                                    <td className="commentPhone">{comment.Phone}</td>
                                    <td className="commentEmail">{comment.Email}</td>
                                    <td className="commentCRUD">
                                        <a onClick={() => this.handleCommentDelete(comment.Id)}>
                                            <i className="fa fa-trash" />
                                        </a>
                                    </td>
                                </tr>
                            );
                        })
                    }
                </tbody>
            </table>
        );
    }
}

export default CommentList;

I'm really beating my head against the wall to understand this... thanks in advance for any help!

Nosnetrom
  • 138
  • 6
  • 1
    `this.loadCommentsFromServer = this.loadCommentsFromServer.bind(this)` – Jackson Feb 26 '20 at 16:08
  • Like @Jackson has pointed out, `loadCommentsFromServer` is not bound to the instance. `this` is probably undefined inside the method. – evolutionxbox Feb 26 '20 at 16:10
  • Did you not see any errors in the console?.. As I would have expected -> `this.props.url` to throw error if the function wasn't bound. – Keith Feb 26 '20 at 16:12
  • `comment.Id = comments.length + 1;` this mutates the current state which you should always avoid. – Emile Bergeron Feb 26 '20 at 16:16
  • [Correct modification of state arrays in ReactJS](https://stackoverflow.com/q/26253351/1218980). [Why can't I directly modify a component's state, really?](https://stackoverflow.com/q/37755997/1218980) – Emile Bergeron Feb 26 '20 at 16:18
  • You delete in the state but read from the props in the child – Cenk Çetinkaya Feb 26 '20 at 17:03

1 Answers1

1

handleCommentDelete should be in the parent component. You can pass the function down to the child from the parent as a prop.

Pass a function to a child component

<CommentList data={this.state.data} handleCommentDelete={this.handleCommentDelete} />

Parent function

handleCommentDelete = (id) => {
    this.setState({
        data: this.state.data.filter(comment => comment.Id !== id)
    });
}

Reference prop function

<a onClick={() => this.props.handleCommentDelete(comment.Id)}>

You are mixing state and props in the child when it should just be props. Pass handleCommentDelete as a prop to the child, call it with this.props.handleCommentDelete(id), and update the state in the parent to reflect the delete. This will then rerender the child with the new list of comments.

amcquaid
  • 539
  • 2
  • 9