4주차 - 댓글 작성하기
본문 바로가기
항해 중/5주차 리액트 심화반

4주차 - 댓글 작성하기

by 은돌1113 2021. 12. 2.

1. 댓글 작성하기 - <PostDetail /> 고치기

더보기

👉 댓글을 작성기능을 만들 때 어떤 순서로 만들면 될까요?

→ 댓글 작성하는 뷰를 만들고, 댓글 텍스트를 리덕스에 넣는 게 첫번째!

그 다음으로는 파이어스토어에 저장하는 기능을 만들어서 진짜 저장까지 해주면 됩니다.

 

그런데, 우리 데이터를 보면 게시글 정보에도 댓글 갯수가 저장되고 있죠!

앗, 그럼 댓글을 작성하면 게시글 정보에 있는 댓글 갯수도 +1 해줘야겠네요!

댓글을 작성하면서 게시글 정보도 같이 수정하도록 해봅시다!

 

- <PostDetail /> 고치기 (firestore 데이터를 리덕스에 넣어 두도록)

 

1) post.js에서 게시글 하나 정보 가져오는 함수 만들기

...
const getOnePostFB = (id) => {
  return function(dispatch, getState, {history}){
    const postDB = firestore.collection("post");
    postDB
      .doc(id)
      .get()
      .then((doc) => {
        let _post = doc.data();

        if (!_post) {
          return;
        }

        let post = Object.keys(_post).reduce(
          (acc, cur) => {
            if (cur.indexOf("user_") !== -1) {
              return {
                ...acc,
                user_info: { ...acc.user_info, [cur]: _post[cur] },
              };
            }
            return { ...acc, [cur]: _post[cur] };
          },
          { id: doc.id, user_info: {} }
        );

        dispatch(setPost([post], { start: null, next: null, size: 3 }));
      });
  }
}
...

export default handleActions(
  {
   [SET_POST]: (state, action) =>
      produce(state, (draft) => {

        draft.list.push(...action.payload.post_list);

        // post_id가 같은 중복 항목을 제거합시다! :)
        draft.list = draft.list.reduce((acc, cur) => {
          // findIndex로 누산값(cur)에 현재값이 이미 들어있나 확인해요!
          // 있으면? 덮어쓰고, 없으면? 넣어주기!
          if (acc.findIndex((a) => a.id === cur.id) === -1){
            return [...acc, cur];
          }else{
            acc[acc.findIndex((a) => a.id === cur.id)] = cur;
            return acc;
          }
        }, []);

				// paging이 있을 때만 넣기
        if (action.payload.paging) {
          draft.paging = action.payload.paging;
        }
        draft.is_loading = false;
      }),
   ...
  },
  initialState
);

const actionCreators = {
  setPost,
  addPost,
  editPost,
  getPostFB,
  addPostFB,
  editPostFB,
  getOnePostFB,
};
...

 

2) 상세 페이지에 적용하기

import React from "react";
import Post from "../components/Post";
import CommentList from "../components/CommentList";
import CommentWrite from "../components/CommentWrite";

import { useSelector, useDispatch } from "react-redux";

import {actionCreators as postActions} from "../redux/modules/post";

const PostDetail = (props) => {
  const dispatch = useDispatch();
  const id = props.match.params.id;

  const user_info = useSelector((state) => state.user.user);

  const post_list = useSelector((store) => store.post.list);

  const post_idx = post_list.findIndex((p) => p.id === id);
  const post = post_list[post_idx];

  React.useEffect(() => {
    if (post) {
      return;
    }

    dispatch(postActions.getOnePostFB(id));
  }, []);

  return (
    <React.Fragment>
      {post && (
        <Post {...post} is_me={post.user_info.user_id === user_info?.uid} />
      )}
      <CommentWrite post_id={id} />
      <CommentList post_id={id} />
    </React.Fragment>
  );
};

export default PostDetail;

 

3) 게시글 목록 페이지 첫 진입 시, 데이터 가져오는 조건 바꿔주기

// pages/PostList.js
...
React.useEffect(() => {
// 가지고 있는 데이터가 0개, 1개일 때만 새로 데이터를 호출해요.
    if (post_list.length < 2) {
      dispatch(postActions.getPostFB());
    }
  }, []);
...

2. 댓글 가져오기

 

- 게시글 id와, 작성 일시 역순으로 정렬해서 가져오기

const getCommentFB = (post_id = null) => {
  return function (dispatch, getState, { history }) {
    const commentDB = firestore.collection("comment");
		
		// post_id가 없으면 바로 리턴하기!
    if(!post_id){
        return;
    }

    // where로 게시글 id가 같은 걸 찾고,
    // orderBy로 정렬해줍니다.
    commentDB
      .where("post_id", "==", post_id)
      .orderBy("insert_dt", "desc")
      .get()
      .then((docs) => {
        let list = [];
        docs.forEach((doc) => {
          list.push({ ...doc.data(), id: doc.id });
        });
        //   가져온 데이터를 넣어주자!
        dispatch(setComment(post_id, list));
      }).catch(err => {
          console.log("댓글 가져오기 실패!", post_id, err);
      });
  };
};

- 리듀서에 적용하기

[SET_COMMENT]: (state, action) =>
      produce(state, (draft) => {
        // comment는 딕셔너리 구조로 만들어서,
        // post_id로 나눠 보관합시다! (각각 게시글 방을 만들어준다고 생각하면 구조 이해가 쉬워요.)
        draft.list[action.payload.post_id] = action.payload.comment_list;
      }),

- <CommentList/>에서 부르기

// components/CommentList.js
import React from "react";
import {Grid, Image, Text} from "../elements";

import {useDispatch, useSelector} from "react-redux";
import {actionCreators as commentActions} from "../redux/modules/comment";

const CommentList = (props) => {
  const dispatch = useDispatch();
  const comment_list = useSelector(state => state.comment.list);
  
  const {post_id} = props;

  React.useEffect(() => {
    if(!comment_list[post_id]){
      // 코멘트 정보가 없으면 불러오기
      dispatch(commentActions.getCommentFB(post_id));
    }
  }, []);

  // comment가 없거나, post_id가 없으면 아무것도 안넘겨준다!
  if(!comment_list[post_id] || !post_id){
    return null;
  }

  return (
    <React.Fragment>
      <Grid padding="16px">
        {comment_list[post_id].map(c => {
          return (<CommentItem key={c.id} {...c}/>);
        })}
      </Grid>
    </React.Fragment>
  );
};

CommentList.defaultProps = {
  post_id: null
};

export default CommentList;

const CommentItem = (props) => {

    const {user_profile, user_name, user_id, post_id, contents, insert_dt} = props;
    return (
        <Grid is_flex>
            <Grid is_flex width="auto">
                <Image shape="circle"/>
                <Text bold>{user_name}</Text>
            </Grid>
            <Grid is_flex margin="0px 4px">
                <Text margin="0px">{contents}</Text>
                <Text margin="0px">{insert_dt}</Text>
            </Grid>
        </Grid>
    )
}

CommentItem.defaultProps = {
    user_profile: "",
    user_name: "mean0",
    user_id: "",
    post_id: 1,
    contents: "귀여운 고양이네요!",
    insert_dt: '2021-01-01 19:00:00'
}

3. 댓글 추가하기

 

- 댓글 추가하기

 

1) 댓글 입력하면 내용 가져오기

// components/CommentWrite.js
import React from "react";
import Post from "../components/Post";
import CommentList from "../components/CommentList";
import CommentWrite from "../components/CommentWrite";

import { useSelector } from "react-redux";

import { firestore } from "../shared/firebase";

const PostDetail = (props) => {
 ...
  const [contents, setContents] = React.useState("");
 ...
  

  const write = () => {
      if(contents === ""){
          window.alert("댓글을 입력해주세요!");
        return;
      }
			console.log(comment_text);
  }

  return (
    <React.Fragment>
      {post && (
        <Post {...post} is_me={post.user_info.user_id === user_info.uid} />
      )}
      <CommentWrite
        _onChange={(e) => {
          setContents(e.target.value);
        }}
        _onClick={write}
      />
      <CommentList />
    </React.Fragment>
  );
};

export default PostDetail;

 

2) addCommentFB를 만든다.

const addCommentFB = (post_id, contents) => {
  return function (dispatch, getState, { history }) {
    const commentDB = firestore.collection("comment");
    const user_info = getState().user.user;

    let comment = {
      post_id: post_id,
      user_id: user_info.uid,
      user_name: user_info.user_name,
      user_profile: user_info.user_profile,
      contents: contents,
      insert_dt: moment().format("YYYY-MM-DD hh:mm:ss"),
    };

    // firestore에 코멘트 정보를 넣어요!
    commentDB.add(comment).then((doc) => {
      const postDB = firestore.collection("post");
      comment = { ...comment, id: doc.id };

      const post = getState().post.list.find((l) => l.id === post_id);

      //   firestore에 저장된 값을 +1해줍니다!
      const increment = firebase.firestore.FieldValue.increment(1);
      
      // post에도 comment_cnt를 하나 플러스 해줍니다.
      postDB
        .doc(post_id)
        .update({ comment_cnt: increment })
        .then((_post) => {
          dispatch(addComment(post_id, comment));
          // 리덕스에 post가 있을 때만 post의 comment_cnt를 +1해줍니다.
          if (post) {
            dispatch(
              postActions.editPost(post_id, {
                comment_cnt: parseInt(post.comment_cnt) + 1,
              })
            );
          }
        });
    });
  };
};

 

3) 리듀서에 적용하기

[ADD_COMMENT]: (state, action) => produce(state, (draft) => {
        draft.list[action.payload.post_id].push(action.payload.comment);
    }),

 

4) <CommentWrite/>에서 부르기

const write = () => {
    if (comment_text === "") {
      window.alert("댓글을 입력해주세요!");
      return;
    }
    // 파이어스토어에 추가합니다.
    dispatch(commentActions.addCommentFB(post_id, comment_text));
    // 입력된 텍스트는 지우기!
    setCommentText("");
  };

 

5) 댓글 입력 후 입력한 텍스트 지우기

// elements/Input.js
...
	<ElInput
          type={type}
          placeholder={placeholder}
          onChange={_onChange}
          value={value}
        />
...

 

댓글