월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
강좌 외 질문
간단한 웹사이트 하나를 만들고 있는데 node express+ejs로 만들고 있습니다. ejs에 관해서 궁금한게 있습니다. ejs에서 express에서 렌더링해준 객체들을 사용할때 res.render({key: value}); script 태그 내에서도 참조할 수 있나요? <script>const a = <%= key %>; </script> 이런식으로 사용하면 못가져오더라고요.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
질문이 있습니다.
실무 관련해서 만약 SSR 방식을 할 일이 없다고 생각해서 react의 route 방식을 썼는데 고객사가 SSR로 변경해달라고 한다면 next 를 써야하고 그렇다면 어느정도 구조를 변경을 해야하잖아요. 그렇다면 'SSR 로 할 일이 없다라고 생각돼도 생각이 바뀌거나 고객사의 변경요청이 있을 수 있으니 기본적으로 next방식으로 코딩하는게 낫다' 라고 생각이 되는데 어떻게 생각하시나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
안녕하세요. toolkit 코드 쪽에 에러가 있습니다.
안녕하세요. 제로초님. 좋은 강의 잘 듣고 있습니다. 다름이 아니라 toolkit 쪽 코드에 loadPosts 액션쪽 오타랑 에러가 있어서, 어제 밤 수정 후 pull request 요청은 해놨는데 확인 부탁드려도 될까요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
안녕하세요 state 관련 질문이요
export const initialState = { mainPosts: [ { id: 1, User: { id: 1, nickname: "제로초", }, content: "첫 번째 게시글 #해시태그 #익스프레스", Images: [ { src: "https://bookthumb-phinf.pstatic.net/cover/137/995/13799585.jpg?udate=20180726", }, { src: "http://gimg.gilbut.co.kr/book/BN001958/rn_view_BN001958.jpg", }, { src: "https://gimg.gilbut.co.kr/book/BN001998/rn_view_BN001998.jpg", }, ], Comments: [ { User: { nickname: "nero", }, content: "우와 개정판이 나왔군요~", }, { User: { nickname: "hero", }, content: "얼른 사고 싶어요!", }, ], }, ], imagePaths: [], postAdded: false, }; mainPosts,Comments에서 [{}] 이런구조로 작성하셨는데 제가 생각하기에 []배열로 감싸지 않고 {}객체로 바로 써도 괜찮을 거 같았는데 [{}] 이 구조로 작성하신이유가 map함수때 이용하기 위해서인가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
실제 배포한 ip에서는 쿠키가 생성이되지않고 passport중에deserializeUser호출이 안됩니다.
안녕하세요 조현영 선생님 제가지금 해당 강의를 통해서 개인 프로젝트를 진행중인데 문제가 발생이 됬습니다. 클라이언트와 백엔드 다 우분트 환경에서 잘 작동해서 실제 배포한 ip주소로 들어가면 정상적으로 화면이 보여집니다. 클라이언트 서버: 다 작동이 잘 되고 회원가입 기능도 잘 작동해서 실제 해당http://13.125.96.64 쪽으로 요청을 하여 데이터베이스도 잘 생성이되고 됩니다. 근데 문제가 local로 성공적으로 로그인을하면 passport.js 쪽에서 serializeUser 한번 실행을하고 쿠키가 생겨야하는데 생기질 않습니다. 그리고 다시 메인 화면으로 돌아오면 LOAD_MY_INFO_REQUEST action으로 통해서 cookie 와 같이 서버로 전송을해서 deserializeUser도 실행이 되야하는데. deserializeUser도 실행이 안됩니다. 제 로컬에서는 모든게 다 잘 작동합니다. 로컬에서 프론트: 로그인 하면 잘 작동합니다. 로컬에서 백엔드 pm2 환경 보시면 쿠키도 잘 생성되고 passport.js 에서 deserializeUser 도 잘 작동되는게 보입니다. 하지만 배포한 ip에서는 쿠키도 생성이 되질않고 deserializeUser 도 작동하지 않습니다위 사진은 제가 로그인이 성공하고 다시 메인 페이지로 돌아온 화면입니다 성공적으로 로그인이 되도 쿠키는 여전히 생성되지 않았습니다. 위 사진은 제가 로그인이 성공하고 다시 메인 페이지로 돌아온 화면입니다 성공적으로 로그인이 되도 쿠키는 여전히 생성되지 않았습니다. 백엔드쪽 봐도serializeUser 으로 로그인이 성공하고 deserializeUser는 호출이 되질 않습니다. back/app.js 소스코드입니다. const express = require('express'); const PORT = 80; const app = express(); const cors = require('cors'); const effectRouter = require('./routers/effect'); const userRouter = require('./routers/user'); const effectsRouter = require('./routers/effects'); const db = require('./models'); const session = require('express-session'); const cookieParser = require('cookie-parser'); const dotenv = require('dotenv'); const passport = require('passport'); const passportConfig = require('./passport'); const morgan = require('morgan'); const hpp = require('hpp'); const helmet = require('helmet'); db.sequelize .sync() .then(() => { console.log('db 연결 성공'); }) .catch(console.error); passportConfig(); dotenv.config(); app.use(cookieParser(process.env.EFFECTSHOP_SECRET)); app.use( session({ saveUninitialized: false, resave: false, secret:process.env.EFFECTSHOP_SECRET, }) ) if(process.env.NODE_ENV === 'production'){ app.use(morgan('combined')); app.use(hpp()); //보안에 도움되는 라이브러리 app.use(helmet()); }else{ app.use(morgan('dev')); } app.use(passport.initialize()); // 패스포트 설정 미들웨어에 추가. app.use(passport.session()); app.use(express.json()); app.use(express.urlencoded({extended: true})); app.use(cors({ origin:["http://localhost:3000","http://54.180.81.148"], credentials:true, })); app.get('/',(req,res)=>{ res.send('hello express'); }) app.use('/effects' ,effectsRouter); app.use('/effect',effectRouter); app.use('/user', userRouter); app.listen(PORT, ()=>{ console.log(`server on! at http://localhost:${PORT}`); }); 더 자세한 코드를 보시길 원하신다면 여기에 제 소스코드를 올려 놓았습니다 ㅠㅠ 엄청난 고민끝에 여쭤봅니다 ㅜ 감사합니다. https://github.com/sungmin-choi/effectShopByHTML-CSS
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
ssr 개념 관한 질문
강의 진행하다가 앞부분 개념이 헷갈려 다시 듣고 왔는데 궁금한 점이 있어 여쭤 봅니다. ssr시 첫 번째 로딩만 브라우저 -> 프론트 서버 -> 벡엔드 -> 디비 이런식으로 전통적인 방법으로 데이터를 받아오고 이후에는 그냥 spa 방식으로 동작한다 라고 이해했는데 질문 1. 그럼 저희 프로젝트에서 saga에서 api 요청을 보내고 받아오는건 브라우저에서 벡엔드로 요청을 보내고 받아오는 것이라고 이해하는 게 맞는지 궁금합니다. 2. 저희 프로젝트에서 npm run dev 로 프론트 서버 실행후localhost:3060 이런 주소로 프론트 서버에 요청을 보내면 벡엔드에서 데이터 요청 후 데이터가 들어있는 정적 파일을받아 오는게 CSR 와의 차이라고 이해했는데 이 데이터를 받아온다는게 pages 폴더 안에 있는 index.js말하는 건가요? 아님 front 단에 작성된 모든 coponent들과 page들을 전부 들고와서 메모리에 캐싱 한다는 말인가요? 나름 구글링하고 찾아 봤는데 명쾌하게 이해되지가 않네요 혹시 강의 뒷부분에 다루어지는 내용이라면 죄송합니다.. 매번 감사드려요
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
git repository 변경 방법
안녕하세요 제로초님 현재 aws ec2 2개 활용하여, 프론트 및 백 배포하였고 두 서버 모두 nginx를 활용하였습니다. 궁금한 점이 기존 aws에 clone한 repo를 지우고 새로운 repo로 clone해도 배포에 문제가 없을까요? 고려할 부분을 미리 생각해보았는데, nginx와 .env 2가지 가 떠오르는데 배포 전에 질문 드립니다. nginx 설정은 변경이 필요없을까요? .env는 추가 예정입니다. 항상 감사드립니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
_app.js 관련 질문입니다.
안녕하십니까 강의를 다 듣고 리뷰를 하는 도중 궁금한 점이 있어 질문 드립니다 ! 1. next.js docs 나 구글에 검색한 대부분의 경우 app.js에서 props로 Component와 함께 {...pageProps}를 같이 받아오는데 이는 _app.js에서 getInitialProps를 사용하거나 SSR 적용시 swr에서 필요한 props를 받아오는 경우가 아닌이상 사용할 필요가 없어서 이번 강의에는 사용하지 않으신건지 궁금합니다. 2. getInitialProps를 _document.js 에서는 적용해주셨는데 이는 모든 페이지에 getInitialProps를 적용하기 위함이라고 생각하면 될까요? (모든 페이지에 styled-components(CSS)를 SSR 하기 위함일까요?)
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
제발도와주세요 TypeError: Cannot read properties of undefined (reading 'data') 에러가 날 때 어떻게 해결하는지요?
제로초님 바쁘신데 항상 질문 확인해주셔서 감사합니다. 혼자서 해결하기 어려워서 질문 드립니다. export부분 랜더링이 안되는것은 커뮤니티 다른분 질문답변으로 고차함수로 바꾸어주어서 되었는데 이 에러는 커뮤니티 질문목록에는 없네요.. 혼자서 해결이 너무 힘들어서 부탁드립니다. 힌트라도 좀 주셨으면 좋겠습니다. 수강생분들중에 아시는분 있으시면 제발 도와주세요.. user> [id].js export부분 export const getServerSideProps = wrapper.getServerSideProps( (store) => async ({ req, params }) => { const cookie = req ? req.headers.cookie : ''; axios.defaults.headers.Cookie = ''; if (req && cookie) { axios.defaults.headers.Cookie = cookie; } store.dispatch({ type: LOAD_USER_POSTS_REQUEST, data: params.id, }); store.dispatch({ type: LOAD_MY_INFO_REQUEST, }); store.dispatch({ type: LOAD_USER_REQUEST, data: params.id, }); store.dispatch(END); await store.sagaTask.toPromise(); console.log('getState', context.store.getState().post.mainPosts); return { props: {} }; }); sagas?post.js function loadPosts 부분 function loadUserPostsAPI(data, lastId) { return axios.get(`/user/${data}/posts?lastId=${lastId || 0}`); } function* loadUserPosts(action) { try { const result = yield call(loadUserPostsAPI, action.data, action.lastId); yield put({ type: LOAD_USER_POSTS_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: LOAD_USER_POSTS_FAILURE, error: err.response.data, }); } }
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
npm run build 에러
front 에서 npm run build 를 하려는데 다음과 같은 에러가 발생했어요 Cannot find module 'webpack' 부분을 보고 package.json 을 보니 webpack 이 안깔려있는 것을 확인하고 npm i webpack 으로 깐 뒤 커밋, 푸쉬하고 우분투 front 서버에서 git pull 까지 한 후 다시 npm run build 를 해도 저런에러가 뜨네요.. ubuntu@ip-172-31-14-223:~/NodeBird/front$ npm run build> react-nodebird-front@1.0.0 build /home/ubuntu/NodeBird/front> cross-env ANALYZE=true NODE_ENV=production next build(node:20352) UnhandledPromiseRejectionWarning: Error: Cannot find module 'webpack'Require stack:- /home/ubuntu/NodeBird/front/node_modules/@next/react-refresh-utils/ReactRefreshWebpackPlugin.js- /home/ubuntu/NodeBird/front/node_modules/next/dist/build/webpack-config.js- /home/ubuntu/NodeBird/front/node_modules/next/dist/build/index.js- /home/ubuntu/NodeBird/front/node_modules/next/dist/cli/next-build.js- /home/ubuntu/NodeBird/front/node_modules/next/dist/bin/next at Function.Module._resolveFilename (internal/modules/cjs/loader.js:902:15) at Function.Module._load (internal/modules/cjs/loader.js:746:27) at Module.require (internal/modules/cjs/loader.js:974:19) at require (internal/modules/cjs/helpers.js:101:18) at Object.<anonymous> (/home/ubuntu/NodeBird/front/node_modules/@next/react-refresh-utils/ReactRefreshWebpackPlugin.js:3:19) at Module._compile (internal/modules/cjs/loader.js:1085:14) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10) at Module.load (internal/modules/cjs/loader.js:950:32) at Function.Module._load (internal/modules/cjs/loader.js:790:12) at Module.require (internal/modules/cjs/loader.js:974:19)(Use `node --trace-warnings ...` to show where the warning was created)(node:20352) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)(node:20352) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. { "name": "react-nodebird-front", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "next -p 3060", "build": "cross-env ANALYZE=true NODE_ENV=production next build", "start": "cross-env NODE_ENV=production next start -p 3060" }, "author": "JoHyeJin", "license": "ISC", "dependencies": { "@ant-design/icons": "^4.7.0", "@next/bundle-analyzer": "^12.1.0", "antd": "^4.18.3", "axios": "^0.26.0", "babel-plugin-styled-components": "^2.0.6", "cross-env": "^7.0.3", "faker": "^4.1.0", "immer": "^9.0.12", "moment": "^2.29.1", "next": "^9.5.5", "next-redux-wrapper": "^7.0.5", "prop-types": "^15.8.1", "react": "^16.14.0", "react-dom": "^16.14.0", "react-redux": "^7.2.6", "react-slick": "^0.28.1", "redux": "^4.1.2", "redux-devtools-extension": "^2.13.9", "redux-saga": "^1.1.3", "shortid": "^2.2.16", "styled-components": "^5.3.3", "swr": "^1.2.2" }, "devDependencies": { "babel-eslint": "^10.1.0", "eslint": "^8.6.0", "eslint-config-airbnb": "^19.0.4", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", "webpack": "^5.70.0" } }
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
Likers 관련 에러 혼자해결해보려고햇지만 결국 안되네요
제로초님 바쁘신데 항상 감사합니다. 커뮤니티 다른 유사한 질문들 보면서 해결해보려고 했는데 저는 안되네요, 강의 그대로 따라하는데 비스한 오류같은데 어디서 잘못된걸까요 도와주세요.. 프론트앤드서버쪽 콘솔입니다 Warning: MenuItem should not leave undefined `key`. 3. getProps after dispatches has store state { user: { loadMyInfoLoading: false, loadMyInfoDone: true, loadMyInfoError: null, loadUserLoading: false, loadUserDone: false, loadUserError: null, followLoading: false, followDone: false, followError: null, unfollowLoading: false, unfollowDone: false, unfollowError: null, logInLoading: false, logInDone: false, logInError: null, logOutLoading: false, logOutDone: false, logOutError: null, signUpLoading: false, signUpDone: false, signUpError: null, changeNicknameLoading: false, changeNicknameDone: false, changeNicknameError: null, loadFollowingsLoading: false, loadFollowingsDone: false, loadFollowingsError: null, loadFollowersLoading: false, loadFollowersDone: false, loadFollowersError: null, removeFollowerLoading: false, removeFollowerDone: false, removeFollowerError: null, me: null, userInfo: null }, post: { mainPosts: [ [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object] ], singlePost: null, imagePaths: [], hasMorePosts: true, likePostLoading: false, likePostDone: false, likePostError: null, unlikePostLoading: false, unlikePostDone: false, unlikePostError: null, loadPostLoading: false, loadPostDone: false, loadPostError: null, loadPostsLoading: false, loadPostsDone: true, loadPostsError: null, addPostLoading: false, addPostDone: false, addPostError: null, removePostLoading: false, removePostDone: false, removePostError: null, addCommentLoading: false, addCommentDone: false, addCommentError: null, uploadImagesLoading: false, uploadImagesDone: false, uploadImagesError: null, retweetLoading: false, retweetDone: false, retweetError: null } } {} 4. WrappedApp created new store with withRedux(AvoCode) { initialState: undefined, initialStateFromGSPorGSSR: { user: { loadMyInfoLoading: false, loadMyInfoDone: true, loadMyInfoError: null, loadUserLoading: false, loadUserDone: false, loadUserError: null, followLoading: false, followDone: false, followError: null, unfollowLoading: false, unfollowDone: false, unfollowError: null, logInLoading: false, logInDone: false, logInError: null, logOutLoading: false, logOutDone: false, logOutError: null, signUpLoading: false, signUpDone: false, signUpError: null, changeNicknameLoading: false, changeNicknameDone: false, changeNicknameError: null, loadFollowingsLoading: false, loadFollowingsDone: false, loadFollowingsError: null, loadFollowersLoading: false, loadFollowersDone: false, loadFollowersError: null, removeFollowerLoading: false, removeFollowerDone: false, removeFollowerError: null, me: null, userInfo: null }, post: { mainPosts: [Array], singlePost: null, imagePaths: [], hasMorePosts: true, likePostLoading: false, likePostDone: false, likePostError: null, unlikePostLoading: false, unlikePostDone: false, unlikePostError: null, loadPostLoading: false, loadPostDone: false, loadPostError: null, loadPostsLoading: false, loadPostsDone: true, loadPostsError: null, addPostLoading: false, addPostDone: false, addPostError: null, removePostLoading: false, removePostDone: false, removePostError: null, addCommentLoading: false, addCommentDone: false, addCommentError: null, uploadImagesLoading: false, uploadImagesDone: false, uploadImagesError: null, retweetLoading: false, retweetDone: false, retweetError: null } } } HYDRATE { type: '__NEXT_REDUX_WRAPPER_HYDRATE__', payload: { user: { loadMyInfoLoading: false, loadMyInfoDone: true, loadMyInfoError: null, loadUserLoading: false, loadUserDone: false, loadUserError: null, followLoading: false, followDone: false, followError: null, unfollowLoading: false, unfollowDone: false, unfollowError: null, logInLoading: false, logInDone: false, logInError: null, logOutLoading: false, logOutDone: false, logOutError: null, signUpLoading: false, signUpDone: false, signUpError: null, changeNicknameLoading: false, changeNicknameDone: false, changeNicknameError: null, loadFollowingsLoading: false, loadFollowingsDone: false, loadFollowingsError: null, loadFollowersLoading: false, loadFollowersDone: false, loadFollowersError: null, removeFollowerLoading: false, removeFollowerDone: false, removeFollowerError: null, me: null, userInfo: null }, post: { mainPosts: [Array], singlePost: null, imagePaths: [], hasMorePosts: true, likePostLoading: false, likePostDone: false, likePostError: null, unlikePostLoading: false, unlikePostDone: false, unlikePostError: null, loadPostLoading: false, loadPostDone: false, loadPostError: null, loadPostsLoading: false, loadPostsDone: true, loadPostsError: null, addPostLoading: false, addPostDone: false, addPostError: null, removePostLoading: false, removePostDone: false, removePostError: null, addCommentLoading: false, addCommentDone: false, addCommentError: null, uploadImagesLoading: false, uploadImagesDone: false, uploadImagesError: null, retweetLoading: false, retweetDone: false, retweetError: null } } } error - components\PostCard.js (70:21) @ PostCard TypeError: Cannot read properties of null (reading 'Likers') 68 | }, [id]); 69 | > 70 | const liked = post.Likers.find((v) => v.id === id); | ^ 71 | return ( 72 | <div style={{ marginBottom: 20 }}> 73 | <Card error - components\PostCard.js (70:21) @ PostCard TypeError: Cannot read properties of null (reading 'Likers') 68 | }, [id]); 69 | > 70 | const liked = post.Likers.find((v) => v.id === id); | ^ 71 | return ( 72 | <div style={{ marginBottom: 20 }}> ------------------------------------------------ 여기는 back> routes> post.js router.get 관련코드입니다. router.get('/:postId/', async (req, res, next) => { // GET / post/1 try { const post = await Post.findOne({ where: { id: req.params.postId }, }); if (!post) { return res.status(404).send('존재하지 않는 게시글입니다.'); } const fullPost = await Post.findOne({ where: { id: post.id }, include: [{ model: Post, as: 'Retweet', include: [{ model: User, attributes: ['id', 'nickname'], }, { model: Image, }] }, { model: User, attributes: ['id', 'nickname'], }, { model: User, as: 'Likers', attributes: ['id', 'nickname'], }, { model: Image, }, { model: Comment, include: [{ model: User, attributes: ['id', 'nickname'], }] },{ model: User, as: 'Likers', attributes: ['id'], }], }); res.status(200).json(fullPost); } catch(error) { console.error(error); next(error); } });
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
dispatch 관련 에러
제로초님, 밑에 해진님이 올리신 질문과 같은 상황이 생겨서 코드를 이렇게 바꾸어주니 정상적으로 로딩이 됩니다. 강의대로 하면 에러가 나는 이유가 무엇인가요?> 기존코드 : 고차함수로 바꾸어준 코드
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
moment 질문
굉장히 사소한 질문이긴 한데요, 제가 moment 를 실험삼아 써보다가 페북이나 유튜브처럼 방금, ~분전 등으로 표시되는 fromNow() 를 써봤는데 분명 방금 쓴 댓글인데 10분이나 22분전으로 막 표시되는 등 시간 정확성이 떨어지는데 이유가 뭘까요?? 다른 것들은 시간 표시가 잘 되었는데 말이죠 moment(post.createdAt).startOf('hour').fromNow()
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
해시태그와 이미지 업로드가 안됩니다.
해시태그를 달고 이미지 첨부해서 올려도 게시글과 사진이 서버로 넘어가지 않는거 같습니다. Request 요청은 가는데 그 이후로 처리가 안되네요. MySQL Workbench에서 새로고침눌러봐도 게시글 해시태그 이미지 모두 안올라 가집니다.. const express = require('express'); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const { Post, Image, Comment, User, Hashtag } = require('../models'); const { isLoggedIn } = require('./middlewares'); const router = express.Router(); try { fs.accessSync('uploads'); } catch (error) { console.log('uploads 폴더가 없으므로 생성합니다.'); fs.mkdirSync('uploads'); } const upload = multer({ storage: multer.diskStorage({ destination(req, file, done) { done(null, 'uploads'); }, filename(req, file, done) { const ext = path.extname(file.originalname); // 확장자 추출(png) const basename = path.basename(file.originalname, ext); // 레니 done(null, basename + '_' + new Date().getTime() + ext); // 레니15335436332.png }, }), limits: { fileSize: 20 * 1024 * 1024 }, // 20MB }); router.post('/', isLoggedIn, upload.none(), async (req, res, next) => { try { const hashtags = req.body.content.match(/#[^\s#]+/g); const post = await Post.create({ content: req.body.content, UserId: req.user.id, }); if (hashtags) { const result = await Promise.all(hashtags.map((tag) => Hashtag.findOrCreate({ where: {name: tag.slice(1).toLowerCase() }, }))); // [[노드, true], [리액트, true]] await post.addHashtags(result.map((v) => v[0])); } if (req.body.image) { if (Array.isArray(req.body.image)) { // 이미지를 여러개 올리면 배열로 올라감 const images = await Promise.all(req.body.image.map((image) => Image.create({ src: image }))); // map을 해서 시퀄라이즈 create 만들어주세요. await post.addImages(images); } else { // 이미지를 하나만 올리면 image: 레니.png const image = await Image.create({ src: req.body.image }); await post.addImages(image); } } const fullPost = await Post.findOne({ where: { id: post.id }, include: [{ model: Image, }, { model: Comment, include: [{ model: User, // 댓글 작성자 attributes: ['id', 'nickname'], }], }, { model: User, // 게시글 작성자 attributes: ['id', 'nickname'], }, { model: User, // 좋아요 누른 사람 as: 'Likers', attributes: ['id'], }] }) res.status(201).json(fullPost); } catch(error) { console.error(error); next(error); } }); router.post('/images', isLoggedIn, upload.array('image'), async (req, res, next) => { // POST /post/images console.log(req.files); res.json(files.map((v) => v.filename)); }); router.post('/:postId/comment', isLoggedIn, async (req, res, next) => { // POST / post/1/comment try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send('존재하지 않는 게시글입니다.'); } const comment = await Comment.create({ content: req.body.content, PostId: parseInt(req.params.postId, 10), // reducer > post.js > ADD_COMMENT_SUCCESS -> action.data.PostId 대소문자 일치시켜주어야 한다! UserId: req.user.id, }) // const fullComment = await Comment.findOne({ // where: { id: comment.id }, // include: [{ // model: User, // attributes: ['id', 'nickname'], // }], // }) res.status(201).json(comment); } catch(error) { console.error(error); next(error); } }); router.patch('/:postId/like', isLoggedIn, async (req, res, next) => { // PATCH /post/1/like try { const post = await Post.findOne({ where: { id: req.params.postId }}); if (!post) { return res.status(403).send('게시글이 존재하지 않습니다.'); } await post.addLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id}); } catch (error) { console.error(error); next(error); } }); router.delete('/:postId/like', isLoggedIn, async (req, res, next) => { // DELETE /post/1/like try { const post = await Post.findOne({ where: { id: req.params.postId }}); if (!post) { return res.status(403).send('게시글이 존재하지 않습니다.'); } await post.removeLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id}); } catch (error) { console.error(error); next(error); } }); router.delete('/:postId', isLoggedIn, async (req, res) => { // DELETE /post/10 try { await Post.destroy({ where: { id: req.params.postId, UserId: req.user.id, }, }); res.status(200).json({ PostId: parseInt(req.params.postId, 10) }); } catch(error) { console.error(error); next(error); } }); module.exports = router;
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
이미지 업로드시 에러
항상 빠른 답변 감사합니다. 이미지 업로드시 에러가 나면서 강좌 돌려가면서 반복적으로 다시 보고 잇는데 어디가 오류있는지 아직 못찾아서 질문드려요.. import React, { useCallback, useEffect, useRef } from 'react'; import { Form, Input, Button } from 'antd'; import { useDispatch, useSelector } from 'react-redux'; import { addPost, UPLOAD_IMAGES_REQUEST, REMOVE_IMAGE } from '../reducers/post'; import useInput from '../hooks/useInput'; const PostForm = () => { const { imagePaths, addPostDone } = useSelector((state) => state.post); const dispatch = useDispatch(); const { text, onChangeText, setText } = useInput(''); // 커스텀 훅 만들어놓은거 자주 활용하기! useEffect(() => { if (addPostDone) { setText(''); } }, [addPostDone]); const onSubmit = useCallback(() => { dispatch(addPost(text)); }, [text]); const imageInput = useRef(); const onClickImageUpload = useCallback(() => { imageInput.current.click(); }, [imageInput.current]); const onChangeImages = useCallback((e) => { console.log('images', e.target.files); const imageFormData = new FormData(); [].forEach.call(e.target.files, (f) => { imageFormData.append('image', f); }); dispatch({ type: UPLOAD_IMAGES_REQUEST, data: imageFormData, }); }); const onRemoveImage = useCallback((index) => () => { dispatch({ type: REMOVE_IMAGE, data: index, }); }); return ( <Form style={{ margin: '10px 0 20px' }} encType="multipart/form-data" onFinish={onSubmit}> <Input.TextArea value={text} onChange={onChangeText} maxLength={140} placeholder="What happen?" /> <div> <Input type="file" name="image" multiple hidden ref={imageInput} onChange={onChangeImages} /> <Button onClick={onClickImageUpload}>이미지 업로드</Button> <Button type="primary" style={{ float: 'right' }} htmlType="submit">Add Story</Button> </div> <div> {imagePaths.map((v, i) => ( <div key={v} style={{ display: 'inline-block' }}> <img src={`http://localhost:3065/${v}`} style={{ width: '200px' }} alt={v} /> <div> <Button onClick={onRemoveImage(i)}>제거</Button> </div> </div> ))} </div> </Form> ); }; export default PostForm;
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
user/[id] 페이지에서 retweet 시 질문입니다.
항상 강의 잘 듣고 질문에 답 잘해주셔서 감사합니다. Avatar 클릭 시 Link로 user/[id] 로 라우팅 되는 것을 구현한 후 user/[id] 페이지에서 Retweet을 하면 [id]의 유저가 아닌 로그인한 id의 post 임에도 불구하고 user/[id] posts 목록에 Retweet 되었다는 글이 추가됩니다. 물론 새로고침하면 다시 사라집니다. Retweet 시 mainPosts에 data가 추가되는데 그 이후에 다시 LOAD_USER_POSTS_REQUEST가 호출되어 화면에 posts들이 보여지는데 이 때 data에 id 넘어가면서 where 조건에 걸려 Retweet한 글은 안보여져야 하지 않나요? (backend 부분을 확인해보니 Retweet 시 retweet에 관한 요청은 확인이 되지만 LOAD_USER_POSTS_REQUEST에 관한 요청은 안들어오는 것 같습니다.) 이 부분이 궁금해서 글 남깁니다 !
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
닉네임 변경이 안됩니다..
이렇게 Request 만 가고 실제로 데이터는 바뀌지 않는데요 어디가 문제일까요? import { Form, Input } from 'antd'; import React, { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import useInput from '../hooks/useInput'; import { CHANGE_NICKNAME_REQUEST } from '../reducers/user'; const NicknameEditForm = () => { const { me } = useSelector((state) => state.user); const [nickname, onChangeNickname] = useInput(me?.nickname || ''); const dispatch = useDispatch(); const onSubmit = useCallback(() => { dispatch({ type: CHANGE_NICKNAME_REQUEST, data: nickname, }); }, [nickname]); return ( <Form style={{ marginBottom: '20px', border: '1px solid #d9d9d9', padding: '20px' }}> <Input.Search value={nickname} onChange={onChangeNickname} addonBefore="닉네임" enterButton="수정" onSearch={onSubmit} /> </Form> ); }; export default NicknameEditForm;
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
닉네임바꾸려고 할때 profile.js 에서 에러
이런 에러 뜰 때 어떻게 하나요?
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
게시글 작성도 안됩니다..
게시글 작성하려고 하면 서버 콘솔이나 브러우저 콘솔창에 아무 에러메시지도 안나오고 ADD_POST_REQUEST 액션만 보내지는데요 어떻게 해결해야 할까요...?? components > PostForm.js 에서 onSubmit 을 강의대로 따라쳐봐도 안되서 // 주석처리하고 github master브랜치 가서 그쪽 코드 넣어봐도 안되네요. 게시글이 안올라가요.. return alert('게시글을 작성하세요.'); 여기 '게시글을 작성하세요.' 이 오류만 계속나오네요
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
SSR, react-intersection-observer 적용 시 문제
안녕하십니까 좋은 강의 및 질문 답변에 항상 감사드립니다. 다름이 아니라 공지사항에 있는 글을 읽고 posts의 scrolling 을 react-intersection-observer를 적용해 보았습니다. 문제는 SSR 적용시 첫 로딩화면에서 scrolling 시 LOAD_POSTS_REQUEST가 dispatch되지 않는 것입니다. console을 찍어보니 useEffect 조건 중 hasMorePosts와 loadPostsLoading은 각각 true, false로 만족하는데 lastId가 undefined가 나옵니다. redux를 확인해봤을 때는 mainPosts의 글들이 보이는데 lastId는 왜 undefined인지 모르겠습니다.. 혹시 ref나 inView 때문인가요? 처음 로딩시 redux 입니다. (context.store.dispatch에 LOAD_POSTS_REQUEST를 넣어봐도 scrolling이 적용되지 않습니다.) 아래는 index.js의 코드입니다. const Home = () => { const dispatch = useDispatch(); const { me } = useSelector((state) => state.user); const { mainPosts, hasMorePosts, loadPostsLoading, retweetError } = useSelector((state) => state.post); useEffect(() => { if (retweetError) { alert(retweetError); } }, [retweetError]); const [ref, inView] = useInView(); useEffect( () => { // 사용자가 마지막 요소를 보고 있고, 로딩 중이 아니라면.. if (inView && hasMorePosts && !loadPostsLoading) { console.log('hasMorePosts', hasMorePosts); console.log('loadPostsLoading', loadPostsLoading); const lastId = mainPosts[mainPosts.length - 1]?.id; console.log('lastId', lastId); console.log('mainPosts', mainPosts); console.log('mainPosts[mainPosts.length - 1]', mainPosts); console.log('lastId', lastId); dispatch({ type: LOAD_POSTS_REQUEST, lastId, }); } }, [inView, hasMorePosts, loadPostsLoading, mainPosts], ); return ( <AppLayout> {me && <PostForm />} {mainPosts.map((post) => <PostCard key={post.id} post={post} />)} <div ref={hasMorePosts && !loadPostsLoading ? ref : undefined} /> </AppLayout> ); }; export const getServerSideProps = wrapper.getServerSideProps(async (context) => { // backend server에 쿠키 전달 const cookie = context.req ? context.req.headers.cookie : ''; axios.defaults.headers.Cookie = ''; if (context.req && cookie) { axios.defaults.headers.Cookie = cookie; } context.store.dispatch({ type: LOAD_MY_INFO_REQUEST, }); // 위 코드까지 진행하면 Request만 진행하고 다시 client 페이지로 돌아온다. // 따라서 Success 까지 진행시키고 데이터를 받아오는 소스가 필요 context.store.dispatch(END); // sagaTask는 configureStore.js 에서 정의 await context.store.sagaTask.toPromise(); }); export default Home;