inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

React로 NodeBird SNS 만들기

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

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

181

stefan CHO

작성한 질문수 37

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;

react javascript

답변 3

0

stefan CHO

넵 답변 감사합니다!

0

제로초(조현영)

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

0

stefan CHO

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

next 10 이상에서는 redux dev tool 구동이 안되나요?

0

272

1

세션 갱신 문의 건

0

482

7

배포 진행 후 Highlight updates components render 표시

0

445

1

똑같은 기능을 하는 테이블

0

447

4

관계형

0

312

2

프론트 서버를 이용하지 않는경우

1

299

3

인피니트 스크롤링 사용시 오류

0

278

0

계속 이런에러가 떠서 해결하기는 했는데 어떤 의미인지 모르겠습니다.

0

433

2

req.user가 언제 생성되나요??

0

330

2

Cannot read property 'id' of null 에러

0

333

1

리트윗한 게시글 불러오는 sequelize

0

252

1

result.data에서 images인 이유

0

281

2

takeLatest에 대한 질문입니다.

1

342

2

프론트에서 express를 사용하지 않을때 동적라우팅

0

501

6

getInitialProps가 클라이언트에서 수행되는 이유?

0

258

1

리로드하면 팔로우 언팔로우 값이 초기화 되는 문제입니다.

0

445

2

스타일드 컴포넌트와 className을 통한 스타일 적용의 차이에 대해 궁금합니다

0

585

2

할인 쿠폰 사용이 안되는되요 (848-f9af83f183e3)

0

365

1

nodejs mvc 패턴

0

975

4

사용하고 보니, 람다 구성이 궁금합니다!

0

266

1

제로초님

0

445

1

새로고침 로그인 풀림 문제.

0

247

1

안녕하세요. 강의 너무 감사합니다

0

157

1

제로초님

0

170

1