작성
·
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
0
참고로 warning은 에러가 아닙니다. 그냥 단순한 경고일 뿐입니다. dummyData는 id가 1로 고정이고 createdAt도 형변환 시 object Object로 떠서 문제가 되는 겁니다. dummy를 쓰지 마시고 실제 데이터 넣는 강좌로 넘어가세요.
0
reducer/post.js에 있는 dummyPost가 mainPosts에 실제로는 추가 되는게 아니라서 map 랜더링이 안되는 것 같은데 맞을까요? 아예 mainPosts에 여러 포스트를 추가하면 카드마다 제대로 추가가 되서요