What You'll Learn

If you're full stack.

Requirements

git clone git@github.com:coderschool/cs-coderbook-template.git
cd cs-coderbook/client
touch .env
echo REACT_APP_BACKEND_API="http://localhost:5000/" > .env
cd ..
cd server
touch .env
echo "PORT=5000 \nJWT_SECRET_KEY='verySecretSecret' \nMONGODB_URI='mongodb://localhost:27017/coder-book'" >> .env
yarn
yarn dev
cd ..
cd client
yarn
yarn start

💡 One or more professional/senior developers have spent months if not years working on the code of the company you join. It's a good idea to study it carefully.

The auth page has a register button/modal that doesn't work. Oppotunity.

Make it work by searching for STEP 1 or look inside of ./client/src/pages/AuthPage/AuthPage.js. Refactor it to update the user state and submit to our API.

<Form
  onSubmit={onSubmit}
  className="d-flex flex-column justify-content-center"
></Form>

This should look familiar, we dispatch an action, register(), to our Redux store. If you look at it you'll see we make a request to our backend.

const onSubmit = (e) => {
  e.preventDefault();
  dispatch(authActions.register(null, user.email, user.password));
};

The form provided doesn't work yet. Bummer.

Make it work. Search the project for the string STEP 2 or look inside of ./client/src/components/Composer/Composer.js.

import React, { useState } from "react";
const [post, setPost] = useState({ body: "" });
const onChange = (e) => {
  setPost({ ...post, [e.target.id]: e.target.value });
};
<Form.Control
  id="body"
  type="text"
  onChange={onChange}
  placeholder="What's on your mind?"
/>

Add a console.log in the body of Composer.

console.log({ post });
const onSubmit = (e) => {
  e.preventDefault();
};
<Form onSubmit={onSubmit}></Form>

We need to dispatch an action when we submit the form.

import { postActions } from "../../redux/actions";
const onSubmit = (e) => {
  e.preventDefault();
  dispatch(postActions.createPost(post.body));
};
import { useDispatch } from "react-redux";
const dispatch = useDispatch();

We've created posts in our backend so let's read from it. Search the project for the string STEP 3 or look inside of ./client/src/pages/HomePage/HomePage.

When the HomePage loads we want to dispatch an action that will fetch posts.

import { useSelector, useDispatch } from "react-redux";
import { postActions } from "../../redux/actions/post.actions";
const dispatch = useDispatch();
dispatch(postActions.postsRequest());

When we refresh the page we see an error. Why...?

We're making a GET request to /api/posts and our server responds 404 Not Found...

We haven't...

router.get("/", postsController.list);

The error went away... and we're getting this data back from the api; interesting...

[
  {
    foo: "bar",
  },
];
postController.list = async (req, res) => {
  return sendResponse(
    res,
    200,
    true,
    { posts: [{ foo: "bar" }] },
    null,
    "Received posts"
  );
};

The data sent back to the client is hardcoded, that isn't right.

Add the owner of the post to the response data by populating as well. Why might we want to know who the owner of a post is?

postController.list = catchAsync(async (req, res) => {
  const posts = await Post.find({}).populate("owner");
  return sendResponse(res, 200, true, { posts }, null, "Received posts");
});

We not get back the correct posts. Incredible

const posts = useSelector((state) => state.post.posts);
{
  posts &&
    posts.map((p) => {
      return <Post key={p._id} {...p} />;
    });
}

We capture the first argument sent into our Post component, props. We then render the body prop and pass the owner prop to PostHeader as a prop named userWhoCreatedPost.

export default function Post(props) {
  return (
    <Card className="p-3 mb-3 rounded">
      <PostHeader userWhoCreatedPost={props.owner} />
      {props.body}
      <Card.Img
        variant="top"
        src="https://images.unsplash.com/photo-1529231812519-f0dcfdf0445f?ixid=MXwxMjA3fDB8MHxzZWFyY2h8Mnx8dGFsZW50ZWR8ZW58MHx8MHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60"
      />
      <PostReactions />
      <hr className="my-1" />
      <PostActions />
      <hr className="mt-1" />
      <PostComments comments={COMMENTS} />
      <CommentForm />
    </Card>
  );
}

Once again, we capture the first prop sent in. Except we also destructure it. The prop we look for is named userWhoCreatedPost. Why?

Afterwards we replace Charles Lee with {userWhoCreatedPost.name}.

function PostHeader({ userWhoCreatedPost }) {
  return (
    <div className="d-flex align-items-center p-3">
      <Avatar url="https://scontent.fsgn5-6.fna.fbcdn.net/v/t1.0-1/p480x480/13924881_10105599279810183_392497317459780337_n.jpg?_nc_cat=109&ccb=3&_nc_sid=7206a8&_nc_ohc=uI6aGTdf9vEAX8-Aev9&_nc_ht=scontent.fsgn5-6.fna&tp=6&oh=e8b18753cb8aa63937829afe3aa916a7&oe=6064C685" />
      <h3 className="font-weight-bold ml-3">{userWhoCreatedPost.name}</h3>
    </div>
  );
}

We should now see that our post is rendering the correct owner's name & the body of the post. Excellent

Now for commenting on a post.

We need to add state to the CommentForm to collect the body of the comment, then send that data to our API after the user submits.

import React, { useState } from "react";
const [comment, setComment] = useState("");
onChange={(e) => setComment(e.target.value)}
import { useDispatch } from "react-redux";
const dispatch = useDispatch();
const onSubmit = (e) => {
  e.preventDefault();
  dispatch();
};
onSubmit = { onSubmit };

Think about where we should look to find the actions we need to dispatch...

We don't have any redux actions related to comments...

This file will contain all the logic to making requests to our API.

import * as types from "../constants/comment.constants";
import api from "../api";
export const CREATE_COMMENT = "POST.CREATE_COMMENT";
export const CREATE_COMMENT_SUCCESS = "POST.CREATE_COMMENT_SUCCESS";
export const CREATE_COMMENT_FAILURE = "POST.CREATE_COMMENT_FAILURE";
const createComment = (postId, body) => async (dispatch) => {
  dispatch({ type: types.CREATE_COMMENT, payload: null });
  try {
    const res = await api.post(`/posts/${postId}/comments`, {
      body,
    });
    dispatch({
      type: types.CREATE_COMMENT_SUCCESS,
      payload: res.data.data,
    });
  } catch (error) {
    dispatch({ type: types.CREATE_COMMENT_FAILURE, payload: error });
  }
};
export const commentActions = {
  createComment,
};
export * from "./comment.actions";
import { commentActions } from "../../redux/actions";

The function createComment() needs two arguments...

dispatch(commentActions.createComment());
<CommentForm postId={props._id} />
const CommentForm = (props) => {
  const onSubmit = (e) => {
    e.preventDefault();
    dispatch(commentActions.createComment(props.postId, comment));
  };
};

We now see if we create a comment our API responds with a 404... Why...?

We haven't defined a POST route for /posts/:id/comments.

router.post(
  "/:id/comments",
  authMiddleware.loginRequired,
  postsController.createComment
);

We create a comment given the data from the front end. We then update the post's comments with the new comment's id. Then we collect the comments of the post and send it back.

postController.createComment = async (req, res) => {
  const comment = await Comment.create({
    ...req.body,
    owner: req.userId,
    post: req.params.id,
  });

  const post = await Post.findById(req.params.id);
  post.comments.push(comment._id);

  await post.save();
  await post.populate("comments");
  await post.execPopulate();

  return sendResponse(res, 200, true, { post }, null, "Comment created!");
};
const Comment = require("../models/Comment");

We're now able to create comments... incredible...