Thinkster
React
CRUD Examples with React & Redux

Example 2: Implementing Create Functionality for Comments

Next, let's implement the 'CommentContainer' component. 'CommentContainer' is just going to be a thin wrapper around a 'CommentList' component.

Create src/components/Article/CommentContainer.js
import CommentInput from './CommentInput';
import CommentList from './CommentList';
import { Link } from 'react-router';
import React from 'react';

const CommentContainer = props => {
  if (props.currentUser) {
    return (
      <div className="col-xs-12 col-md-8 offset-md-2">
        <div>
          <ListErrors errors={props.errors}></ListErrors>
          <CommentInput slug={props.slug} currentUser={props.currentUser} />
        </div>

        <CommentList
          comments={props.comments}
          slug={props.slug}
          currentUser={props.currentUser} />
      </div>
    );
  } else {
    return (
      <div className="col-xs-12 col-md-8 offset-md-2">
        <p>
          <Link to="login">Sign in</Link>
          &nbsp;or&nbsp;
          <Link to="register">sign up</Link>
          &nbsp;to add comments on this article.
        </p>

        <CommentList
          comments={props.comments}
          slug={props.slug}
          currentUser={props.currentUser} />
      </div>
    );
  }
};

export default CommentContainer;

Mostly what this component will do is show the correct header on top of the 'CommentList' component based on whether the user is logged in. First, let's implement the 'CommentInput' component. To implement 'CommentInput', first we need to add an HTTP call to agent.js to create a comment:

Update src/agent.js
// ...
const Comments = {
  create: (slug, comment) =>
    requests.post(`/articles/${slug}/comments`, { comment }),
  forArticle: slug =>
    requests.get(`/articles/${slug}/comments`)
};
// ...

Next, let's put this HTTP call into the mapDispatchToProps() function for the CommentInput component.

Create src/components/Article/CommentInput.js
import React from 'react';
import agent from '../../agent';
import { connect } from 'react-redux';

const mapDispatchToProps = dispatch => ({
  onSubmit: payload =>
    dispatch({ type: 'ADD_COMMENT', payload })
});

class CommentInput extends React.Component {
  constructor() {
    super();
    this.state = {
      body: ''
    };

    this.setBody = ev => {
      this.setState({ body: ev.target.value });
    };

    this.createComment = ev => {
      ev.preventDefault();
      const payload = agent.Comments.create(this.props.slug,
        { body: this.state.body });
      this.setState({ body: '' });
      this.props.onSubmit(payload);
    };
  }

  render() {
    return (
      <form className="card comment-form" onSubmit={this.createComment}>
        <div className="card-block">
          <textarea className="form-control"
            placeholder="Write a comment..."
            value={this.state.body}
            onChange={this.setBody}
            rows="3">
          </textarea>
        </div>
        <div className="card-footer">
          <img
            src={this.props.currentUser.image}
            className="comment-author-img" />
          <button
            className="btn btn-sm btn-primary"
            type="submit">
            Post Comment
          </button>
        </div>
      </form>
    );
  }
}

export default connect(() => ({}), mapDispatchToProps)(CommentInput);

Next let's handle the 'ADD_COMMENT' action in the 'article' reducer:

Update src/reducers/article.js
'use strict';

export default (state = {}, action) => {
  switch (action.type) {
    // ...
    case 'ADD_COMMENT':
      return {
        ...state,
        commentErrors: action.error ? action.payload.errors : null,
        comments: action.error ?
          null :
          (state.comments || []).concat([action.payload.comment])
      };
  }

  return state;
};
 

I finished! On to the next chapter