If you're full stack.
git clone git@github.com:coderschool/cs-coderbook-template.git
root
, add .env
file, add variables.cd cs-coderbook/client
touch .env
echo REACT_APP_BACKEND_API="http://localhost:5000/" > .env
root
, add .env
file, add a few variables.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.
onSubmit()
as prop onSubmit
to the form inside of the Modal Form
.<Form
onSubmit={onSubmit}
className="d-flex flex-column justify-content-center"
></Form>
onSubmit
in the body of the component.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
.
useState
provided by React.import React, { useState } from "react";
post
state variable.const [post, setPost] = useState({ body: "" });
onChange()
which will be invoked when the user types into the form control of the form.const onChange = (e) => {
setPost({ ...post, [e.target.id]: e.target.value });
};
Form.Control
to have two additional props, id
& onChange
.<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 });
onSubmit()
which will invoke when the user presses enter.const onSubmit = (e) => {
e.preventDefault();
};
onSubmit()
as the onSubmit
prop to the Form
.<Form onSubmit={onSubmit}></Form>
postActions
from our actions.We need to dispatch an action when we submit the form.
import { postActions } from "../../redux/actions";
onSubmit()
to dispatch an action.const onSubmit = (e) => {
e.preventDefault();
dispatch(postActions.createPost(post.body));
};
useDispatch()
since we need to dispatch an action to redux.import { useDispatch } from "react-redux";
dispatch()
inside the body of Composer
.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
.
useDispatch()
& our postActions
.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";
dispatch()
in the body of HomePage
.const dispatch = useDispatch();
postActions.postsRequest()
inside the body of the useEffect()
.dispatch(postActions.postsRequest());
We're making a GET
request to /api/posts
and our server responds 404
Not Found...
./server/routes/posts.api
.GET
request and map it to our list
controller action.router.get("/", postsController.list);
[
{
foo: "bar",
},
];
list
action inside of ./server/controllers/posts.controller.js
.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");
});
posts
data in HomePage
by selecting it from our Redux store.const posts = useSelector((state) => state.post.posts);
Post
component for each post.{
posts &&
posts.map((p) => {
return <Post key={p._id} {...p} />;
});
}
Post
to use the actual data we get from our API inside of ./client/src/components/Post/Post.js
.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>
);
}
PostHeader
to consume the props we just sent it.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>
);
}
Now for commenting on a post.
STEP 4
or look inside of ./client/src/components/Post/Post.js
.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.
useState()
hook.import React, { useState } from "react";
comment
state var.const [comment, setComment] = useState("");
onChange
prop of the Form.Control
.onChange={(e) => setComment(e.target.value)}
import { useDispatch } from "react-redux";
dispatch()
in the body of CommentForm
.const dispatch = useDispatch();
onSubmit()
which will be invoked when the form submits.const onSubmit = (e) => {
e.preventDefault();
dispatch();
};
onSubmit()
to the form's onSubmit
prop.onSubmit = { onSubmit };
./client/src/redux/actions
.We don't have any redux actions related to comments...
comment.actions.js
in ./client/src/redux/actions
.This file will contain all the logic to making requests to our API.
import * as types from "../constants/comment.constants";
import api from "../api";
./client/src/redux/comment.constants.js
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,
};
./client/src/redux/actions/index.js
export * from "./comment.actions";
commentActions
into our Post
component.import { commentActions } from "../../redux/actions";
dispatch()
The function createComment()
needs two arguments...
dispatch(commentActions.createComment());
postId
to the comment form from the Post
body.<CommentForm postId={props._id} />
CommentForm
and send it along with comment
to createComment()
.const CommentForm = (props) => {
const onSubmit = (e) => {
e.preventDefault();
dispatch(commentActions.createComment(props.postId, comment));
};
};
./server/routes/posts.api.js
.We haven't defined a POST
route for /posts/:id/comments
.
router.post(
"/:id/comments",
authMiddleware.loginRequired,
postsController.createComment
);
createComment()
in the controller.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!");
};
Comment
as we use it.const Comment = require("../models/Comment");