인프런 커뮤니티 질문&답변

stefan CHO님의 프로필 이미지
stefan CHO

작성한 질문수

React로 NodeBird SNS 만들기

4-16. 댓글 작성 리덕스 사이클

댓글이 첫번째 포스트카드에만 붙습니다.

작성

·

147

0

원인파악이 잘안되네요..

post.id가 카드를 생성해도 계속 1로만 있는것 같은데 이유를 잘 모르겠습니다. 아래 코드인데 한번 봐주실 수 있을까요.. 아니면 디버깅 방법이라도 알려주시면 감사하겠습니다. 선택한카드에 댓글버턴을 눌렀을때 선택한 카드를 콘솔에 찍는방법이 잘 안떠오릅니다.

콘솔에는 아래와 같은 에러가 뜹니다.

Warning: Encountered two children with the same key, `[object Object]`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
    in Home (at _app.js:24)
    in div (created by Context.Consumer)
    in Col (at AppLayout.js:54)
    in div (created by Context.Consumer)
    in Row (at AppLayout.js:50)
    in AppLayout (at _app.js:23)
    in Provider (at _app.js:15)
    in NodeBird (created by withRedux(NodeBird))
    in withRedux(NodeBird)
    in Container (created by AppContainer)
    in AppContainer

***** postcard.js ==================

import React, { useState, useCallback, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { ADD_COMMENT_REQUEST } from "../reducers/post";
import { Card, Button, Avatar, Input, Comment, List, Form } from "antd";
import {
  RetweetOutlined,
  HeartOutlined,
  MessageOutlined,
  EllipsisOutlined,
} from "@ant-design/icons";

const PostCard = ({ post }) => {
  const [commentFormOpened, setCommentFormOpened] = useState(false);
  const [commentText, setCommentText] = useState("");
  const { me } = useSelector((state) => state.user);
  const { isAddedComment, isAddingComment } = useSelector(
    (state) => state.post
  );
  const dispatch = useDispatch();

  useEffect(() => {
    console.log("effect");
    setCommentText("");
  }, [isAddedComment === true]);

  const onCommentToggle = useCallback(() => {
    setCommentFormOpened((prev) => !prev);
  }, []);

  const onChangeCommentText = useCallback((e) => {
    setCommentText(e.target.value);
  }, []);

  const onSubmitComment = useCallback(() => {
    if (!me) {
      return alert("Please Login First");
    }
    console.log("submit");
    console.log(post.id);
    return dispatch({ type: ADD_COMMENT_REQUEST, data: { postId: post.id } });
  }, [me && me.id]);

  return (
    <div>
      <Card
        key={+post.createdAt}
        hoverable
        style={{ width: 240, padding: 10, marginTop: 10 }}
        cover={post.img && <img alt={post} src={post.img} />}
        actions={[
          <RetweetOutlined />,
          <HeartOutlined />,
          <MessageOutlined onClick={onCommentToggle} />,
          <EllipsisOutlined />,
        ]}
        extra={<Button>Delete</Button>}
      >
        <Card.Meta
          title={post.User.nickName}
          description={post.content}
        ></Card.Meta>
      </Card>
      {commentFormOpened && (
        <React.Fragment>
          <Form onFinish={onSubmitComment}>
            <Form.Item>
              <Input.TextArea
                rows={4}
                value={commentText}
                onChange={onChangeCommentText}
              />
            </Form.Item>
            <Button type="primary" htmlType="submit" loading={isAddingComment}>
              Reply
            </Button>
          </Form>
          <List
            header={`${post.Comments ? post.Comments.length : 0} 댓글`}
            itemLayout="horizontal"
            dataSource={post.Comments || []}
            renderItem={(item) => (
              <li>
                <Comment
                  author={item.User.nickName}
                  avatar={<Avatar>{item.User.nickName[0]}</Avatar>}
                  content={item.content}
                />
              </li>
            )}
          />
        </React.Fragment>
      )}
    </div>
  );
};

export default PostCard;

***** index.js==================

import React from "react";
import Postform from "../components/postform";
import PostCard from "../components/postcard";
import { useSelector } from "react-redux";

const Home = () => {
  const { mainPosts } = useSelector((state) => state.post);
  const { isLogged } = useSelector((state) => state.user);
  return (
    <React.Fragment>
      {isLogged && <Postform />}
      {mainPosts.map((v) => {
        return <PostCard key={v} post={v} />;
      })}
    </React.Fragment>
  );
};

export default Home;

***** sagas/post.js ==================

import { delay, fork, all, takeLatest, put } from "redux-saga/effects";
import {
  ADD_POST_REQUEST,
  ADD_POST_SUCCESS,
  ADD_POST_FAILURE,
  ADD_COMMENT_REQUEST,
  ADD_COMMENT_SUCCESS,
  ADD_COMMENT_FAILURE,
} from "../reducers/post";

// ACTION_API, ACTION_NAME, ACTION_WATCH are set
function* addPostAPI() {
  // return axios.post('/post')
}

function* addPost() {
  try {
    yield delay(2000);
    yield put({ type: ADD_POST_SUCCESS });
  } catch (e) {
    console.log(e);
    yield put({ type: ADD_POST_FAILURE, postErrorReason: e });
  }
}
// add Comment
function* addPostWatch() {
  yield takeLatest(ADD_POST_REQUEST, addPost);
}

function* addCommentAPI() {
  // return axios.post('/post')
}

function* addComment(action) {
  try {
    yield delay(2000);
    yield put({
      type: ADD_COMMENT_SUCCESS,
      data: { postId: action.data.postId },
    });
  } catch (e) {
    console.log(e);
    yield put({ type: ADD_COMMENT_FAILURE, commentErrorReason: e });
  }
}

function* addCommentWatch() {
  yield takeLatest(ADD_COMMENT_REQUEST, addComment); // dispatch할때 action값이 addComment로 넘어가는듯
}

export default function* postSaga() {
  yield all([fork(addPostWatch), fork(addCommentWatch)]);
}

***** reducer/post.js ==================

export const ADD_POST_REQUEST = "ADD_POST_REQUEST";
export const ADD_POST_SUCCESS = "ADD_POST_SUCCESS";
export const ADD_POST_FAILURE = "ADD_POST_FAILURE";
export const ADD_COMMENT_REQUEST = "ADD_COMMENT_REQUEST";
export const ADD_COMMENT_SUCCESS = "ADD_COMMENT_SUCCESS";
export const ADD_COMMENT_FAILURE = "ADD_COMMENT_FAILURE";

export const initialState = {
  mainPosts: [
    {
      id: 1,
      User: {
        id: 1,
        nickName: "",
      },
      content: "",
      img: "https://img.icons8.com/plasticine/2x/image.png",
      Comments: [],
    },
  ],
  imagePaths: [],
  isAddingPost: false,
  isAddedPost: false,
  postErrorReason: "",
  isAddingComment: false,
  isAddedComment: false,
  commentErrorReason: "",
};

const dummyPost = {
  id: 2,
  User: {
    id: 1,
    nickName: "dum cho",
  },
  content: "dummy Post",
  Comments: [],
  img: "https://img.icons8.com/plasticine/2x/image.png",
};
const dummyComment = {
  id: 1,
  User: {
    id: 1,
    nickName: "dum cho",
  },
  Comments: "this is dummy",
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_POST_REQUEST:
      return {
        ...state,
        mainPost: action.data,
        isAddedPost: false,
        isAddingPost: true,
      };
    case ADD_POST_SUCCESS:
      return {
        ...state,
        mainPosts: [dummyPost, ...state.mainPosts],
        isAddedPost: true,
        isAddingPost: false,
      };
    case ADD_POST_FAILURE:
      return {
        ...state,
        mainPost: action.data,
        isAddedPost: false,
        isAddingPost: false,
      };
    case ADD_COMMENT_REQUEST:
      return {
        ...state,
        isAddedComment: false,
        isAddingComment: true,
      };
    case ADD_COMMENT_SUCCESS: {
      const postIndex = state.mainPosts.findIndex(
        (v) => v.id === action.data.postId
      );
      // console.log(action.data.postId, postIndex);
      const post = state.mainPosts[postIndex];
      const Comments = [...post.Comments, dummyComment];
      const mainPosts = [...state.mainPosts];
      mainPosts[postIndex] = { ...post, Comments };
      return {
        ...state,
        mainPosts,
        isAddedComment: true,
        isAddingComment: false,
      };
    }
    case ADD_COMMENT_FAILURE:
      return {
        ...state,
        mainPost: action.data,
        isAddedComment: false,
        commentErrorReason: false,
      };
    default:
      return { ...state };
  }
};

export default reducer;

답변 3

0

stefan CHO님의 프로필 이미지
stefan CHO
질문자

넵 답변 감사합니다!

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

참고로 warning은 에러가 아닙니다. 그냥 단순한 경고일 뿐입니다. dummyData는 id가 1로 고정이고 createdAt도 형변환 시 object Object로 떠서 문제가 되는 겁니다. dummy를 쓰지 마시고 실제 데이터 넣는 강좌로 넘어가세요.

0

stefan CHO님의 프로필 이미지
stefan CHO
질문자

reducer/post.js에 있는 dummyPost가 mainPosts에 실제로는 추가 되는게 아니라서 map 랜더링이 안되는 것 같은데 맞을까요? 아예 mainPosts에 여러 포스트를 추가하면 카드마다 제대로 추가가 되서요

stefan CHO님의 프로필 이미지
stefan CHO

작성한 질문수

질문하기