🚨 서비스 장애 발생 🙇🏻‍♀️ 🙇🏻

2020년 8월 5일 18시11분 부터 약 25분간 장애가 발생했습니다.
불편드려 죄송합니다. (자세히 보기)

[리뉴얼] React로 NodeBird SNS 만들기
[리뉴얼] React로 NodeBird SNS 만들기
수강정보
(6개의 수강평)
209명의 수강생
song 프로필

미들웨어 관련하여.. song 2시간 전
그러면 middleware는 어떤 action이든간에 dispatch되기전에 무조건 일어하는 선행 행위 같은 거라고 생각하면 되나요 ?ㅜㅜ 만일  middleware 여러개라면 return next(action)을 통해 모든 middleware을 실행한 뒤에 비로소야 dispatch가 실행되는거죠 ? 필요한 이유는 서버와의 동작같이 비동기 적으로 일어나는 경우에 데이터를 주고 받은 뒤에 dispatch 되어야 하니까 필요한 거구요! 어렵네요 미들웨어 ..

0
OCK Sam 프로필

custom hook useInput 사용에서 질문 있습니다..! OCK Sam 11시간 전
안녕하세요 제로초님. 강의 잘 보고 있습니다..! 이번에 customHook을 사용하는 예제를 실행 했을 때, export default (initialValue = null) => { const [value, setValue] = useState(initialValue); const handler = useCallback( (e) => { setValue(e.target.value); }, []); return [value, handler]; } 를 사용 할 경우 next에서 warning을 하는데요, 내용은 Anonymous arrow functions cause Fast Refresh to not preserve local component state. Please add a name to your function, for example: Before export default () => <div />; After const Named = () => <div />; export default Named; 입니다. 여기 warning에 나온대로 useInput.js의 내용을 const useInput = (initialValue = null) => { const [value, setValue] = useState(initialValue); const handler = useCallback( (e) => { setValue(e.target.value); }, []); return [value, handler]; } export default useInput; 으로 수정해서 사용은 하고 있지만,  이 이슈, 익명 화살표 함수를 사용했을 때 왜 Fast Refresh가 되고, local component state를 왜 preserve 하지 못하는지 궁금해서 질문 드립니다...! next버전은 9.5.1을 사용하고 있습니다..!

0
김희찬 프로필

aws 인스턴스 중지했을 때 김희찬 11시간 전
인스턴스의 임시 스토리지에 있는 데이터는 모두 손실됩니다. 라고 경고 메세지가 뜨는데 괜찮은 건가요? 사용을 안할 땐 꺼놓고 싶은데 데이터가 무사한지..궁금합니다 중지말고 종료를 하면 아예 없어지는지도 궁금합니다.

1
song 프로필

궁금 song 11시간 전
안녕하세요! 리엑트 웹 게임 강좌랑 비교해서 보다보니 사소한  질문이 생겼습니다ㅜㅜ eslintrc의 역할은 문법을 규정해주는 역할을 하는건가요 ? webpack이랑 어떻게 다른가요 ? 추가적으로 next는 wepback 설정이 필요하지 않나요 ?

2
박기홍 프로필

인스턴스 연결 실패 박기홍 14시간 전
인스턴스 연결하려고 터미널에서 ssh 명령어 쳤는데 UNPROTECTED PRIVATE KEY FILE! 뜨면서 연결이 안되는데 이럴땐 어떻게 해야될까요?? pem 파일 받아서 NodeBird_Renewal_Fullstack 폴더에 넣어주고 gitignore로 ignore만 시켜준 다음에 터미널에 명령어 친 상태입니다.

0
roqkfchqh 프로필

dispatch roqkfchqh 17시간 전
안녕하세요 제로초님 강의 정말 잘 듣고 있습니다!!!lastId방식으로 스크롤해서 포스트들 불러오는 곳에서요, useEffect(() => { function onScroll() { if (window.pageYOffset + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) { if (hasMorePosts && !loadPostsLoading) { const lastId = mainPosts[mainPosts.length - 1]?.id; dispatch({ type: LOAD_POSTS_REQUEST, lastId, }); } } } 강의대로 이렇게 하니까 saga로 lastId가 전달이 안돼서 항상 같은 게시물들만 불러오는 오류가 나서 dispatch부분을 dispatch({             type: LOAD_POSTS_REQUEST,             data: lastId,          }); 이렇게 바꾸고 saga쪽도 lastId를 data로 바꾸니까 정상작동 하더라구요 혹시나 해서 lastId: lastId 이렇게 해보니까 그것도 saga로 데이터가 안 보내지구요,,다른 곳에서 오타가 났을 가능성이 있는건가요?

2
castinglife 프로필

좋아요 토글 버튼 구현시 TypeError: Cannot read property 'Likers' of undefined 에러 발생 문의 드립니다. castinglife 17시간 전
로그인후  좋아요 버튼이 1회만 작동하고 상태값이 바뀌지 않습니다.  에러 메세지에서는  TypeError: Cannot read property 'Likers' of undefined 라고 나오는데.. PostCard.js import React, { useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { Card, Popover, Button, Avatar, List, Comment } from 'antd'; import { RetweetOutlined, HeartOutlined, HeartTwoTone, MessageOutlined, EllipsisOutlined } from '@ant-design/icons'; import { useSelector, useDispatch } from 'react-redux'; import PostImages from './PostImages'; import CommentForm from './CommentForm'; import PostCardContent from './PostCardContent'; import { REMOVE_POST_REQUEST, LIKE_POST_REQUEST, UNLIKE_POST_REQUEST } from '../reducers/post'; import FollowButton from './FollowButton'; const PostCard = ({ post }) => { // state.user.me && state.user.me.id => state.user.me?.id const dispatch = useDispatch(); const { removePostLoding } = useSelector((state) => state.post); // const [liked, setLiked] = useState(false); const [commentFormOpened, setCommentFormOpened] = useState(false); const onLike = useCallback(() => { if (!id) { return alert('로그인이 필요합니다.'); } return dispatch({ type: LIKE_POST_REQUEST, data: post.id, }) }, [id]); const onUnLike = useCallback(() => { if (!id) { return alert('로그인이 필요합니다.'); } return dispatch({ type: UNLIKE_POST_REQUEST, data: post.id, }) }, [id]); const onToggleComment = useCallback(() => { setCommentFormOpened((prev) => !prev); }, []); const onRemovePost = useCallback(() => { dispatch({ type: REMOVE_POST_REQUEST, data: post.id, }); }, []) const id = useSelector((state) => state.user.me?.id); const liked = post.Likers.find((v) => v.id === id); return ( <div style={{ marginBottom: 20 }}> <Card cover={post.Images[0] && <PostImages images={post.Images} />} actions={[ <RetweetOutlined key="retweet" />, liked ? <HeartTwoTone twoToneColor="#eb2f96" key="heart" onClick={onUnLike} /> : <HeartOutlined key="heart" onClick={onLike} />, <MessageOutlined key="comment" onClick={onToggleComment} />, <Popover key="more" content={( <Button.Group> {id && post.User.id === id ? ( <> <Button>수정</Button> <Button type="danger" loading={removePostLoding} onClick={onRemovePost}>삭제</Button> </> ) : <Button>신고</Button>} </Button.Group> )}> <EllipsisOutlined /> </Popover>, ]} extra={ id && <FollowButton post={post} />} > <Card.Meta avatar={<Avatar>{post.User.nickname[0]}</Avatar>} title={post.User.nickname} description={<PostCardContent postData={post.content} />} /> </Card> {commentFormOpened && ( <div> <CommentForm post={post} /> <List header={`${post.Comments.length}개의 댓글`} itemLayout="horizontal" dataSource={post.Comments} renderItem={(item) => ( <li> <Comment author={item.User.nickname} avatar={<Avatar>{item.User.nickname[0]}</Avatar>} content={item.content} /> </li> )} /> </div> )} </div> ); }; PostCard.propTypes = { post: PropTypes.shape({ id: PropTypes.number, User: PropTypes.object, content: PropTypes.string, createdAt:PropTypes.string, Comments: PropTypes.arrayOf(PropTypes.object), Images: PropTypes.arrayOf(PropTypes.object), Likers: PropTypes.arrayOf(PropTypes.object), }).isRequired, }; export default PostCard; reducers/post.js import produce from 'immer'; import { REMOVE_POST_OF_ME } from './user'; export const initialState = { mainPosts: [], imagePaths: [], // 이미지를 업로드 할때 이미지 경로 postAdded: false, hasMorePosts: true, likePostLoading: false, likePostDone: false, // 추가 likePostError: null, // 추가 unlikePostLoading: false, unlikePostDone: false, // 추가 unlikePostError: null, // 추가 loadPostsLoading: false, loadPostsDone: false, // 추가 loadPostsError: null, // 추가 // 게시글 추가가 완료되었을때 true 로 변한다. postAdded: false, => addPostLoading: false, addPostLoading: false, addPostDone: false, // 추가 addPostError: null, // 추가 addCommentLoading: false, // 댓글 추가가 완료되었을때 true 로 변한다. addCommentDone: false, // 추가 addCommentError: null, // 추가 removePostLoading: false, removePostDone: false, // 추가 removePostError: null, // 추가 }; export const LOAD_POSTS_REQUEST = 'LOAD_POSTS_REQUEST'; export const LOAD_POSTS_SUCCESS = 'LOAD_POSTS_SUCCESS'; export const LOAD_POSTS_FAILURE = 'LOAD_POSTS_FAILURE'; export const LIKE_POST_REQUEST = 'LIKE_POST_REQUEST'; export const LIKE_POST_SUCCESS = 'LIKE_POST_SUCCESS'; export const LIKE_POST_FAILURE = 'LIKE_POST_FAILURE'; export const UNLIKE_POST_REQUEST = 'UNLIKE_POST_REQUEST'; export const UNLIKE_POST_SUCCESS = 'UNLIKE_POST_SUCCESS'; export const UNLIKE_POST_FAILURE = 'UNLIKE_POST_FAILURE'; 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 REMOVE_POST_REQUEST = 'REMOVE_POST_REQUEST'; export const REMOVE_POST_SUCCESS = 'REMOVE_POST_SUCCESS'; export const REMOVE_POST_FAILURE = 'REMOVE_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 addComment = (data) => ({ type: ADD_COMMENT_REQUEST, data, }); export const addPost = (data) => ({ type: ADD_POST_REQUEST, data, }); export const removePost = (data) => ({ type: REMOVE_POST_OF_ME, data, }); const reducer = (state = initialState, action) => produce(state, (draft) => { switch (action.type) { case LIKE_POST_REQUEST: draft.likePostLoading = true; draft.likePostDone = false; draft.likePostError = null; break; case LIKE_POST_SUCCESS: { const post = draft.mainPosts.find((v) => v.id === action.data.PostId); post.Likers.push({ id: action.data.UserId }); draft.likePostLoading = false; draft.likePostDone = true; break; } case LIKE_POST_FAILURE: draft.likePostLoading = false; draft.likePostError = action.error; break; case UNLIKE_POST_REQUEST: draft.unlikePostLoading = true; draft.unlikePostDone = false; draft.unlikePostError = null; break; case UNLIKE_POST_SUCCESS: { const post = draft.mainPosts.find((v) => v.id === action.data.PostId); post.Likers = post.Likers.filter((v) => v.id !== action.data.UserId); // 제거 draft.unlikePostLoading = false; draft.unlikePostDone = true; break; } case UNLIKE_POST_FAILURE: draft.unlikePostLoading = false; draft.unlikePostError = action.error; break; case LOAD_POSTS_REQUEST: draft.loadPostsLoading = true; draft.loadPostsDone = false; draft.loadPostsError = null; break; case LOAD_POSTS_SUCCESS: draft.loadPostsLoading = false; draft.loadPostsDone = true; draft.mainPosts = action.data.concat(draft.mainPosts); draft.hasMorePosts = draft.mainPosts.length < 50; break; case LOAD_POSTS_FAILURE: draft.loadPostsLoading = false; draft.loadPostsError = action.error; break; case ADD_POST_REQUEST: draft.addPostLoading = true; draft.addPostDone = false; draft.addPostError = null; break; case ADD_POST_SUCCESS: draft.addPostLoading = false; draft.addPostDone = true; draft.mainPosts.unshift(action.data); break; case ADD_POST_FAILURE: draft.addPostLoading = true; draft.addPostError = action.error; break; case ADD_COMMENT_REQUEST: draft.addCommentLoading = true; draft.addCommentDone = false; draft.addCommentError = null; break; case ADD_COMMENT_SUCCESS: const post = draft.mainPosts.find((v) => v.id === action.data.PostId); post.Comments.unshift(action.data); draft.addCommentLoading = false; draft.addCommentDone = true; break; case ADD_COMMENT_FAILURE: draft.addCommentLoading = false; draft.addCommentError = action.error; break; case REMOVE_POST_REQUEST: draft.removePostLoading = true; draft.removePostDone = false; draft.removePostError = null; break; case REMOVE_POST_SUCCESS: draft.removePostLoading = false; draft.removePostDone = true; draft.mainPosts = draft.mainPosts.filter((v) => v.id !== action.data); break; case REMOVE_POST_FAILURE: draft.removePostLoading = false; draft.removePostError = action.error; break; default: break; } }); export default reducer; sagas/post.js import { all, fork, put, takeLatest, delay, throttle, call } from 'redux-saga/effects'; import axios from 'axios'; import { LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, LIKE_POST_REQUEST, LIKE_POST_SUCCESS, LIKE_POST_FAILURE, UNLIKE_POST_REQUEST, UNLIKE_POST_SUCCESS, UNLIKE_POST_FAILURE, ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE, ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE, } from '../reducers/post'; import { ADD_POST_TO_ME, REMOVE_POST_OF_ME, } from '../reducers/user'; function likePostAPI(data) { return axios.patch(`/post/${data}/like`); }; function* likePost(action) { try { const result = yield call(likePostAPI, action.data); yield put({ type: LIKE_POST_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ // put => dispatch 다. type: LIKE_POST_FAILURE, data: err.response.data, }); } } function unlikePostAPI(data) { return axios.delete(`/post/${data}/like`); }; function* unlikePost(action) { try { const result = yield call(unlikePostAPI, action.data); yield put({ type: UNLIKE_POST_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ // put => dispatch 다. type: UNLIKE_POST_FAILURE, data: err.response.data, }); } } function loadPostsAPI(data) { return axios.get('/posts', data); }; function* loadPosts(action) { try { const result = yield call(loadPostsAPI, action.data); yield put({ type: LOAD_POSTS_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ // put => dispatch 다. type: LOAD_POSTS_FAILURE, data: err.response.data, }); } } function addCommentAPI(data) { return axios.post(`/post/${data.postId}/comment`, data); // POST /post/1/comment }; function* addComment(action) { try { const result = yield call(addCommentAPI, action.data); // yield delay(1000); yield put({ type: ADD_COMMENT_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ // put => dispatch 다. type: ADD_COMMENT_FAILURE, data: err.response.data, }); }; }; function addPostAPI(data) { return axios.post('/post', { content: data }); }; function* addPost(action) { try { const result = yield call(addPostAPI, action.data); yield put({ type: ADD_POST_SUCCESS, // data: action.data, data: result.data }); yield put({ type: ADD_POST_TO_ME, data: result.data.id, }); } catch (err) { console.error(err); yield put({ // put => dispatch 다. type: ADD_POST_FAILURE, data: err.response.data, }); }; }; function removePostAPI(data) { return axios.delete(`/api/post/${data.postId}/comment`, data); }; function* removePost(action) { try { // yield put({ // type: 'REMOVE_POST_REQUEST', // }); // const result = yield call(removePostAPI); yield delay(1000); yield put({ type: REMOVE_POST_SUCCESS, data: action.data, }); console.log('removePost'); yield put({ type: REMOVE_POST_OF_ME, data: action.data, }); } catch (err) { console.error(err); yield put({ // put => dispatch 다. type: REMOVE_POST_FAILURE, data: err.response.data, }); }; }; function* watchLoadPosts() { // 5초에 한번 게사글이 로드 된다. yield throttle(5000, LOAD_POSTS_REQUEST, loadPosts); }; function* watchAddPost() { yield takeLatest(ADD_POST_REQUEST, addPost); }; function* watchAddComment() { yield takeLatest(ADD_COMMENT_REQUEST, addComment); }; function* watchRemovePost() { yield takeLatest(REMOVE_POST_REQUEST, removePost); }; function* watchLikePost() { yield takeLatest(LIKE_POST_REQUEST, likePost); }; function* watchUnLikePost() { yield takeLatest(UNLIKE_POST_REQUEST, unlikePost); }; export default function* postSaga() { yield all([ fork(watchLikePost), fork(watchUnLikePost), fork(watchAddPost), fork(watchLoadPosts), fork(watchRemovePost), fork(watchAddComment), ]); };

4
ksrlogic 프로필

CRA에서 eslint했을 때 ksrlogic 19시간 전
CRA에서 강좌처럼 해봤는데 eslint설정을 하면 eslint 버전오류나고 버전을 맞춰주면 eslint가 안 먹습니다 ㅠㅠ CRA는 따로  eslint를 맞추는게 있나요?

1
ksrlogic 프로필

미들웨어 개념이 잘 이해가 안 가요 ksrlogic 1일 전
const loggerMiddleware = ({ dispatch, getState}) =>  (next) => (action) => {   console.log(action);   return next(action) } 1. 여기서 applyMiddleware로 loggerMiddleware를 감싸면 알아서 위 applyMiddleware함수가 dispatch나 getState같은 인자들을 넣어주는 건가요? 2. 그리고 next의 역할은 미들웨어를 끝내 는 일을 하는건가요? 3. 미들웨어는 dispatch가 실행될 때마다 일을 하는거고 위의 action은 dispatch가 일어날 그 당시의 action을 말하는건가요? 너무 어려워요..

1
castinglife 프로필

Uncaught TypeError: Cannot read property '0' of undefined 에러가 발생합니다. 해결방법이 있을까요? castinglife 1일 전
강좌 동영상에 같은 에러를 해결하시는 부분이 나오는데  app.js 에  '/posts'  동일한 라우터가 존재해서 삭제후 해결하시는 부분이 나오는데 ..  다른문제로 보입니다. routes/posts.js  파일  include  Image 를 넣어주어도 해도 해결이 안되네요.. FRONT pages/index.js import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import PostForm from '../components/PostForm'; import PostCard from '../components/PostCard'; import AppLayout from '../components/AppLayout'; import { LOAD_POSTS_REQUEST } from '../reducers/post'; import { LOAD_USER_REQUEST } from '../reducers/user'; const Home = () => { const dispatch = useDispatch(); const { me } = useSelector((state) => state.user); const { mainPosts, hasMorePosts, loadPostsLoading } = useSelector((state) => state.post); useEffect(() => { dispatch({ type: LOAD_USER_REQUEST, }) dispatch({ type: LOAD_POSTS_REQUEST, }); }, []); useEffect(() => { function onScroll() { if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) { if (hasMorePosts && !loadPostsLoading) { dispatch({ type: LOAD_POSTS_REQUEST, }); } } } window.addEventListener('scroll', onScroll); return () => { window.removeEventListener('scroll', onScroll); }; }, [ hasMorePosts, loadPostsLoading, mainPosts]); return ( <AppLayout> {me && <PostForm />} {mainPosts.map((post) => <PostCard key={post.id} post={post} />)} </AppLayout> ); }; export default Home; sagas/post.js import { all, fork, put, takeLatest, delay, throttle, call } from 'redux-saga/effects'; import axios from 'axios'; import { LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE, ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE, } from '../reducers/post'; import { ADD_POST_TO_ME, REMOVE_POST_OF_ME, } from '../reducers/user'; function addCommentAPI(data) { return axios.post(`/post/${data.postId}/comment`, data); // POST /post/1/comment }; function* addComment(action) { try { const result = yield call(addCommentAPI, action.data); yield put({ type: ADD_COMMENT_SUCCESS, data: result.data, }); } catch (err) { yield put({ // put => dispatch 다. type: ADD_COMMENT_FAILURE, data: err.response.data, }); }; }; function loadPostsAPI(data) { return axios.get('/posts', data); }; function* loadPosts(action) { try { const result = yield call(loadPostsAPI, action.data); yield put({ type: LOAD_POSTS_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ // put => dispatch 다. type: LOAD_POSTS_FAILURE, data: err.response.data, }); } } function addPostAPI(data) { return axios.post('/post', { content: data }); }; function* addPost(action) { try { const result = yield call(addPostAPI, action.data); yield put({ type: ADD_POST_SUCCESS, // data: action.data, data: result.data }); yield put({ type: ADD_POST_TO_ME, data: result.data.id, }); } catch (err) { console.error(err); yield put({ type: ADD_POST_FAILURE, data: err.response.data, }); }; }; function removePostAPI(data) { return axios.delete(`/api/post/${data.postId}/comment`, data); }; function* removePost(action) { try { // const result = yield call(removePostAPI); yield delay(1000); yield put({ type: REMOVE_POST_SUCCESS, data: action.data, }); console.log('removePost'); yield put({ type: REMOVE_POST_OF_ME, data: action.data, }); } catch (err) { console.error(err); yield put({ type: REMOVE_POST_FAILURE, data: err.response.data, }); }; }; function* watchLoadPosts() { yield throttle(5000, LOAD_POSTS_REQUEST, loadPosts); }; function* watchAddPost() { yield takeLatest(ADD_POST_REQUEST, addPost); }; function* watchAddComment() { yield takeLatest(ADD_COMMENT_REQUEST, addComment); }; function* watchRemovePost() { yield takeLatest(REMOVE_POST_REQUEST, removePost); }; export default function* postSaga() { yield all([ fork(watchAddPost), fork(watchLoadPosts), fork(watchRemovePost), fork(watchAddComment), ]); }; BACK routes/posts.js const express = require('express'); const { Post, User, Image, Comment } = require('../models/'); const router = express.Router(); router.get('/', async (req, res, next) => { // GET /posts try { const posts = await Post.findAll({ limit: 10, order: [['createdAt', 'DESC']], inculude: [{ model: User, attributes: ['id','nickname'], }, { model: Image, }, { model: Comment, include: [{ model: User, attributes: ['id','nickname'], }], }], }); res.status(200).json(posts); } catch (error) { console.log(error); next(error); } }); module.exports = router; 로그인 창도 안뜨고 에러가 떳었는데. include 를 모두 해주니  정상적으로 로그인도 되고 게시글도 등록됩니다. 하지만 등록후 바로 다시 위와 똑같은 에러가 동일하게 뜹니다. PostCard 에 key 관련 에러로 추측됩니다. BACK routes/posts.js const express = require('express'); const { Post, User, Image, Comment } = require('../models/'); const router = express.Router(); router.get('/', async (req, res, next) => { // GET /posts try { const posts = await Post.findAll({ limit: 10, order: [ ['createdAt', 'DESC'], [Comment, 'createdAt', 'DESC'], ], include: [{ model: User, attributes: ['id', 'nickname'], }, { model: Image, }, { model: Comment, include: [{ model: User, attributes: ['id', 'nickname'], }], }, { model: User, // 좋아요 누른 사람 as: 'Likers', attributes: ['id'], }, { model: Post, as: 'Retweet', include: [{ model: User, attributes: ['id', 'nickname'], }] }], }); res.status(200).json(posts); } catch (error) { console.log(error); next(error); } }); module.exports = router;

3
song 프로필

popover song 1일 전
안녕하세요 제로초님...예제를 조금 변형해 PostCard 를 만드는 작업을 하는 도중에 문제가 생겨서 남깁니다. 기존 action요소 에 있던  <EllipsisOutlined />를 extra로 옮기면서 작업하는 도중에 Popover를 사용할 경우에 무한 콜스택으로 들어가는 문제가 생겼습니다. ㅜㅜ 그래서 Popover를 감싸지 않고  <EllipsisOutlined />를 사용해서 onClick으로 출력 해준 결과에는 잘 작동하는 것을 보였습니다. 어떤게 문제인가요? ㅜㅜPopover가 제가 모르는 다른 call을하는건지 1번코드 사진 2번코드 사진 //1번째 코드 import react, { useState, useCallback } from 'react'; import { Card, Popover, Button, List, Comment, Avatar, Skeleton } from 'antd'; import { useSelector } from 'react-redux'; import { RetweetOutlined, HeartOutlined, MessageOutlined, ShareAltOutlined, EllipsisOutlined } from '@ant-design/icons'; import PostImage from './PostImage'; import CommentForm from './CommentForm'; import PropTypes from 'prop-types'; const POSTCARD = ({ post }) => { console.log(post.User.id); const { me } = useSelector(state => state.user); const [commentOpen, setCommentOpened] = useState(false); const id = me && me.id; console.log(me); console.log(id); const onClickRetWeet = useCallback( () => { }, [], ) const onClickHeart = useCallback( () => { }, [], ) const onClickComment = useCallback( () => { setCommentOpened((pre) => (!pre)); }, [], ) const onClickShared = useCallback( () => { }, [], ) const onClickmore = useCallback( () => { console.log("click more"); }, [], ) return ( <> <Card cover={post.Images[0] && <PostImage images={post.Images} />} actions={[ <RetweetOutlined key="retweet" onClick={onClickRetWeet} />, <HeartOutlined key="edit" onClick={onClickHeart} />, <MessageOutlined key="message" onClick={onClickComment} />, <ShareAltOutlined key="shared" onClick={onClickShared} />, ]} extra={ // <Popover trigger="click" // content={( // <Button.Group> // {id && id === post.User.id ? // <> <Button>수정</Button> <Button>삭제</Button></> // : // <>< Button>신고 </Button></>} // </Button.Group> // )} // > // <EllipsisOutlined /> // </Popover>} <EllipsisOutlined onClick={onClickmore}/> } > <Card.Meta avatar={<Avatar>{post.User.nickname[0]}</Avatar>} title={post.User.nickname} description={post.content} /> </Card > {commentOpen && ( <> <Card > <CommentForm post={post} /> <List style={{ marginTop: "20px" }} header={`${post.Comments.length}개의 댓글`} itemLayout="horizontal" dataSource={post.Comments} renderItem={item => ( <List.Item actions={[<a key="list-loadmore-edit">edit</a>, <a key="list-loadmore-more">more</a>]} > <Skeleton avatar title={false} loading={item.loading} active> <List.Item.Meta avatar={ <Avatar>{item.User.nickname[0]}</Avatar> } title={item.User.nickname} description={item.content} /> </Skeleton> </List.Item> )} /> </Card> </> )} </> ) } POSTCARD.PropTypes = { post:PropTypes.shape({ id: PropTypes.number, User: PropTypes.object, Comments: PropTypes.arrayOf(PropTypes.any), content: PropTypes.string, Images: PropTypes.arrayOf(PropTypes.any), imagePaths: PropTypes.arrayOf(PropTypes.any) }) } export default POSTCARD; ////2번째 코드 import react, { useState, useCallback } from 'react'; import { Card, Popover, Button, List, Comment, Avatar, Skeleton } from 'antd'; import { useSelector } from 'react-redux'; import { RetweetOutlined, HeartOutlined, MessageOutlined, ShareAltOutlined, EllipsisOutlined } from '@ant-design/icons'; import PostImage from './PostImage'; import CommentForm from './CommentForm'; import PropTypes from 'prop-types'; const POSTCARD = ({ post }) => { console.log(post.User.id); const { me } = useSelector(state => state.user); const [commentOpen, setCommentOpened] = useState(false); const id = me && me.id; console.log(me); console.log(id); const onClickRetWeet = useCallback( () => { }, [], ) const onClickHeart = useCallback( () => { }, [], ) const onClickComment = useCallback( () => { setCommentOpened((pre) => (!pre)); }, [], ) const onClickShared = useCallback( () => { }, [], ) // const onClickmore = useCallback( // () => { // console.log("click more"); // }, // [], // ) return ( <> <Card cover={post.Images[0] && <PostImage images={post.Images} />} actions={[ <RetweetOutlined key="retweet" onClick={onClickRetWeet} />, <HeartOutlined key="edit" onClick={onClickHeart} />, <MessageOutlined key="message" onClick={onClickComment} />, <ShareAltOutlined key="shared" onClick={onClickShared} />, ]} extra={ <Popover trigger="click" content={( <Button.Group> {id && id === post.User.id ? <> <Button>수정</Button> <Button>삭제</Button></> : <>< Button>신고 </Button></>} </Button.Group> )} > <EllipsisOutlined /> </Popover>} > <Card.Meta avatar={<Avatar>{post.User.nickname[0]}</Avatar>} title={post.User.nickname} description={post.content} /> </Card > {commentOpen && ( <> <Card > <CommentForm post={post} /> <List style={{ marginTop: "20px" }} header={`${post.Comments.length}개의 댓글`} itemLayout="horizontal" dataSource={post.Comments} renderItem={item => ( <List.Item actions={[<a key="list-loadmore-edit">edit</a>, <a key="list-loadmore-more">more</a>]} > <Skeleton avatar title={false} loading={item.loading} active> <List.Item.Meta avatar={ <Avatar>{item.User.nickname[0]}</Avatar> } title={item.User.nickname} description={item.content} /> </Skeleton> </List.Item> )} /> </Card> </> )} </> ) } POSTCARD.PropTypes = { post:PropTypes.shape({ id: PropTypes.number, User: PropTypes.object, Comments: PropTypes.arrayOf(PropTypes.any), content: PropTypes.string, Images: PropTypes.arrayOf(PropTypes.any), imagePaths: PropTypes.arrayOf(PropTypes.any) }) } export default POSTCARD;

1
B Han 프로필

back 에서 mdels 의 model file 코드에 대해 B Han 2일 전
안녕하세요. 제로초님. 강의를 들으면서 실습시 오류등이 발생하면 제로초님의 github에서 참고하는데요. modes의 파일들은 수업때 진행하신 코드와는 다르게 클래스로  되어있더라고요.  혹시 관련 설명이 수업중에 언급이 되는데 제가 놓친건가 해서 문의드려요~ 즐거운 한주 되세요. 

2
김희찬 프로필

db를 class로 바꾸는 과정에서 define이 정의되지 않았다고 에러가 납니다. 김희찬 2일 전
ㅇsequelize에 define이 정의되지 않았다고 나오는데 뭐가 문제인지 모르겠습니다.

2
ksrlogic 프로필

ant design 메뉴바가 반응형을 방해합니다 ksrlogic 2일 전
메뉴바때문에 옆에 칸이 생겨버립니다.. .이거 너무 불편한데 어떻게 해결하나요ㅋㅋ...

4
김동현 프로필

const dispatch = useDispatch(); 김동현 3일 전
선언하는 이유가 궁금합니다! 그냥 useDispatch(action()); 이렇게 하면 동작이 안되나요?

1
지식공유자 되기
많은 사람들에게 배움의 기회를 주고,
경제적 보상을 받아보세요.
지식공유참여
기업 교육을 위한 인프런
“인프런 비즈니스” 를 통해 모든 팀원이 인프런의 강의들을
자유롭게 학습하는 환경을 제공하세요.
인프런 비즈니스