묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
백엔드 root 서버에서 npx sequelize db:create을 하면 command not found: sequelize이 뜹니다.
안녕하세요! 제로초님[리뉴얼] React로 NodeBird SNS 만들기 강의의섹션 6. AWS에 배포하기: 우분투에 MySQL 설치하기 까지 진행한 수강생 입니다! 저는 windows 10을 사용하고 있으며, 윈도우 서비스에서 MySQL을 실행한 상태입니다.아마존의 front와 back 인스턴스가 잘 실행되고 있음을 확인하였습니다.사전에 AWS 우분투 프론트와 백엔드 서버 두 곳 모두 node와 npm을 설치하였습니다.node 버전은 v14.21.3, npm 버전은 6.14.18을 설치하였습니다.AWS 우분투 백엔드 서버와 root 서버에서 MySQL 8 버전을 완전 삭제한 후 재설치 하였습니다.(원래 우분투 백엔드 서버에만 설치해야 하는데 루트 경로에 설치하는 바람에 재설치를 진행하였습니다!) 사진과 같이 백엔드 root 서버 터미널에 npx sequelize db:create을 입력하면command not found: sequelize (sequalize 명령을 찾을 수 없습니다.)가 뜹니다. 문제를 해결하기 위해서 첫 번째로 sequelize와 sequelize-cil이back 폴더에 제대로 설치가 되어있는지 확인했습니다.아래 사진과 같이 back 폴더의 package.json 파일에서 시퀄라이즈 잘 설치되어 있음을 확인하였습니다. 두 번째로, 우분투 리눅스 루트 command not found: sequelize,npx sequelize db:create command not found: sequelize 해결시퀄라이즈 command not found 해결등등의 command not found: sequelize 관련 키워드를 구글링 하고,노드버드 강의 커뮤니티 글을 보던 중npx sequelize db:create 대신 sudo npx sequelize db:create 사용하는 방법을 찾았습니다.sudo를 사용해 npx sequelize db:create을 시도하였으나여전히 sequelize 명령어가 없다고 뜹니다.어떻게 하면 백엔드 root 서버에서 npx sequelize db:create을 성공할 수 있을까요?혹시 관련 키워드나 힌트를 주실 수 있나요?항상 강의 열심히 듣고 있습니다. 긴 글 읽어주셔서 감사합니다 제로초님!
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
AWS ubuntu 프론트 서버에서 npm run build 가 되지 않습니다!
안녕하세요! 제로초님[리뉴얼] React로 NodeBird SNS 만들기 강의의섹션 6. AWS에 배포하기: 우분투에 노드 설치하기 까지 진행한 수강생 입니다!우분투에 노드 설치하기 강의와 다음 강의인 우분투에 MySQL 설치하기 강의에서도AWS 우분투 프론트 서버에서 npm run build가 성공했음을 확인하지 못하여 질문 글 올립니다!사전에 강의를 진행하면서 AWS 우분투 프론트와 백엔드 서버 두 곳 모두 node와 npm을 설치하였습니다.node 버전은 v14.21.3, npm 버전은 6.14.18을 설치하였습니다.제로초님 강의와 다르게 react-nodebird에 prepare 폴더는 만들지 않습니다.그래서 react-nodebird 폴더 아래에 바로.git, front, back, .gitignore, react-nodebird.pem 파일이 위치해 있습니다. npm run build 를 진행하기 전 우분투 프론트 서버 인스턴스 연결 주소를 정확히 입력하였습니다.ubuntu 프론트 서버에서 git pull과 npm i를 한 다음, npm run build를 진행하였으나빌드가 되지 않고, 에러가 나타났습니다.에러 전체를 번역 후 읽어보니 눈에 띄는 부분은 다음과 같았습니다.Error occurred prerendering page "/about".Read more: https://err.sh/next.js/prerender-error페이지 "/about"을(를) 미리 렌더링하는 동안 오류가 발생했습니다.자세한 내용: https://err.sh/next.js/prerender-errorTypeError: Cannot read property 'data' of undefined유형 오류: 정의되지 않은 속성 '데이터'를 읽을 수 없습니다about을 렌더링하는 동안 오류가 발생했다는 메시지를 확인하고,정적 페이지인 about.js의 getStaticProps를 의심했지만에러 메시지에는 getStaticProps 코드 언급이 없어서 함부로 의심하는 걸 멈췄습니다.https://nextjs.org/docs/messages/prerender-error에러 메시지에 있는 next 사이트를 확인했으나 제 미숙함으로 문제를 해결할 방법을 찾지 못했습니다. npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! 이것은 아마도 npm의 문제가 아닐 것입니다. 위에 추가적인 로깅 출력이 있을 것 같습니다.npm ERR! 이 실행의 전체 로그는 다음에서 확인할 수 있습니다:npm ERR! /home/ubuntu/.npm/_logs/2024-01-17T18_39_56_370Z-debug.logvscode 에서 debug.log를 클릭했으나 No match results가 뜨며 로그를 확인할 수 없었습니다. 문제를 해결하기 위해 커뮤니티를 확인하던 중 똑같은 aws 빌드 문제가 발생한 질문 글을 발견하였습니다.-- 해당 질문글 링크 --https://www.inflearn.com/course/lecture?courseSlug=%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC&unitId=49013&category=questionDetail&tab=community&q=254338해당 질문 글에서 제로초님은 npm i를 다시 하거나styled-components 버전 문제일 가능성을 알려주셨습니다.강의와 똑같이 styled-components ^5.1.1 버전을 사용하고 있음을 확인하였고,다시 우분투 프론트 서버에서 npm install를 한 후, 빌드를 시도해보았으나상단 에러와 똑같은 문제가 발생했습니다.vsCode를 완전히 껐다가 키고, 우분투 인스턴스를 잠시 중시 후 재부팅 해보았음에도같은 문제가 발생하였습니다.node react AWS 구동 방법, AWS E2U 배포, AWS 빌드 문제 npm 관련 키워드를 검색하면서 npm을 실행할 때 종속성이나 캐시 문제 발생이 원인이지 않을까 짐작했습니다.그래서 로컬 front 경로에서 아래 네 가지 작업을 진행하였습니다.(사전에 node_modules 폴더, package.lock.json 파일, .next를 삭제하기 전에로컬 프론트에서 npm run dev, npm run build, npm start 스크립트를 실행해서버가 성공적으로 연결됨을 확인하였습니다.)로컬 front 경로1. node_modules 폴더 삭제2. package.lock.json 삭제3. .next 삭제4. npm cache clean --force 로 npm 캐시 삭제네 가지 작업을 진행한 후 로컬 front에서 다시 npm install을 하여 node_modules과 package.lock.json을 생성하였습니다.다시 aws 우분투 프론트 서버로 돌아와 node, npm이 정상적으로 설치되었음을 확인하고git pull, npm i 를 하고, npm run build를 했지만 문제는 해결되지 않았습니다!어떻게 하면 AWS ubuntu 프론트 서버 경로에서 npm run build를 성공할 수 있을까요?혹시 관련 키워드나 힌트를 주실 수 있나요?aws를 처음 사용해서 미숙함에 질문 글이 많이 길어졌습니다.항상 강의 열심히 듣고 있습니다. 긴 글 읽어주셔서 감사합니다 제로초님!
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
EC2 pem 키를 사용해서 원격에 있는 서버로 접근할 때 WARNING: UNPROTECTED PRIVATE KEY FILE! 문제가 발생합니다!
안녕하세요! 제로초님! 노드버드 섹션6 EC2 생성하기 강의까지 수강한 수강생 입니다!아래 수강생 분과 동일한 문제가 발생하여 구글링과 다른 분들의 질문 글을 보며해결하려 했으나 그러지 못하여 질문을 올리게 되었습니다!항상 친절한 답변을 해주셔서 감사합니다!(강의 항상 잘 보고 있어요!) 같은 문제가 발생한 수강생 분의 질문https://www.inflearn.com/course/lecture?courseSlug=%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC&unitId=49012&tab=community&category=questionDetail&q=152537키의 권한을 바꾸는 방법 참고https://wookim789.tistory.com/34SSH은 22번 포드, HTTP는 80번 포트, HTTPS는 443번 포트로 강의와 동일하게 진행하였습니다.폴더를 하나 더 들어가야 하는 불편함 때문에 강의와 똑같이 prepare 폴더는 만들지 않고,노드버드 폴더 아래에 바로 front, back 폴더가 위치하도록 하였습니다.vs 터미널에서 발생한 문제 메시지를 번역하니 다음과 같았습니다.경고: 보호되지 않는 개인 키 파일react-nodebird.pem'에 대한 권한이 너무 열려 있습니다.다른 사람이 개인 키 파일에 액세스할 수 없어야 합니다. 이 개인 키는 무시됩니다.로드 키 "react-nodebird.pem": 잘못된 권한ubuntu@ec2-13-125-253-222.ap-northeast-2.compute.amazonaws.com : 권한이 거부되었습니다 같은 문제가 발생한 수강생 분의 글을 읽고 vs 터미널이 아닌 git bash를 사용했습니다.이 때 문제를 해결하신 수강생 분과는 다른 메시지가 나타났습니다.(문제를 해결하신 수강생 분의 git bash 상태)(저의 git bash 상태)응용프로그램에 대한 확장 보안 유지관리가 활성화되지 않았습니다.Expanded Security Maintenance for Applications is not enabled.빨간 선은 개인 정보라 부득이하게 블러 처리하였습니다. ssh -i "react-nodebird.pem" ubuntu@ec2-13-125-253-222.ap-northeast-2.compute.amazonaws.com의심이 들어 다시 vs code 터미널의 노드버드 폴더 경로에 위 명령어를 붙여 넣었으나WARNING: UNPROTECTED PRIVATE KEY FILE! 문제가 계속 발생합니다.어떻게 해야 이 문제를 해결할 수 있는지 도움을 구하고 싶습니다!저 또한 답변만 기다리지 않고 계속 해결 방안을 모색하겠습니다!긴 질문 글 읽어주셔서 감사합니다.
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
팔로잉, 팔로워 목록은 잘 불러왔으나 콘솔에 Warning: Failed prop type: The prop `data` is marked as required in `FollowList`, but its value is `undefined`. 에러가 발생합니다!
안녕하세요 제로초님!! 해당 강의 영상을 끝까지 수강한 수강생 입니다!항상 강의 영상 잘 보고 있습니다. 복잡한 질문에도 친절히 답해주셔서 감사합니다~! 첫 번째 에러를 번역해보니 'FollowList'에 prop 'data'가 필요한 것으로 표시되어 있지만 값은 'undefined'입니다. 라고 뜹니다.문제가 생긴 profile.js 에서 console.log(data)를 해보니 역시 data가 undefined 가 나옵니다.FollowList.js의 data가 profile.js로 전달이 되지 않은 것인가요? 만약 그렇다면 어디를 봐야 하는지 힌트 부탁 드립니다!(data의 프롭타입은 배열입니다!)https://github.com/ZeroCho/react-nodebird/blob/master/ch6/front/pages/profile.js질문을 올리기 전에 제로초님 깃허브를 꼼꼼히 참고하고 스스로 구글링 해보았지만 해결이 어려워 질문 올립니다!포기하지 않고 끝까지 완강을 목표로 하겠습니다! 아래 사진은 상단 이미지에서 잘린 에러 전체입니다.감사합니다!
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
https 적용 후에 이미지 주소에서 에러가 발생합니다.
http일 때는 문제가 없었는데 https 적용 후에 문제가 발생합니다.강의에서 알려주신 대로 하면 에러가 나와서 제로초 님 블로그 보고 cerbot이랑 nginx 설치했습니다.아래 코드만 강의와 다르게 했습니다.sudo snap install certbot --classic sudo apt-get install nginxsudo certbot --nginxhttps://www.zerocho.com/category/NodeJS/post/5ef450a5701d8a001f84baeb 에러 내용은 다음과 같습니다.https 적용 전https://abc.s3.ap-northeast-2.amazonaws.com/original/1705127046450_china.jpg https 적용 후https://abc/https:/abc.s3.ap-northeast-2.amazonaws.com/original/1705127046450_china.jpghttp://abc/ 이 부분을 없애는 방법을 알고 싶습니다. nginx.conf는 아래와 같습니다.server { // 실제로는 구매한 도메인 넣었습니다. server_name abc; location / { proxy_set_header HOST $host; proxy_pass http://127.0.0.1:3000/; proxy_redirect off; } }server { // 실제로는 구매한 도메인 넣었습니다. server_name api.abc; location / { proxy_set_header HOST $host; proxy_pass http://127.0.0.1:3000/; proxy_redirect off; } }
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
swr를 사용했을 때 팔로잉, 팔로워 목록을 불러오지 못 하고, 리스트에 limit이 안됩니다!
안녕하세요 제로초님! 노드버드 섹션5 swr 사용해보기 강의까지 진행한 수강생 입니다!항상 질문에 답해주셔서 감사합니다. 올려주신 강의도 잘 보고 있습니다!구글링과 제로초님의 노드버드 깃허브를 꼼꼼히 살펴보았으나 제 지식 부족으로 인하여 질문 올립니다..!swr 라이브러리를 사용 후, 로그인한 다음 프로필 페이지에 가면콘솔 에러가 발생하며, 팔로잉, 팔로워 목록이 없는 문제가 발생합니다.때문에 더 보기 버튼을 클릭해도 더 불러오지 못 했습니다!에러 메시지를 번역하니 다음과 같았습니다.리덕스 탭과 네트워크 탭에서는 에러를 발견하지 못하였습니다!리덕스 탭에서는 해당 사용자의 팔로잉, 팔로워 정보가 들어가 있었고,네트워크 탭에서는 팔로잉, 팔로워 limit이 잘 불러와 집니다.의심이 들어 백엔드 터미널을 확인해보니 팔로잉, 팔로워 목록 limit이 안되고 있었습니다!GET /user/followers?limit=3 401GET /user/followings?limit=3 401에러가 난 코드를 포함해 가장 의심되는 코드를 올립니다!프로필 페이지 profile.js팔로워, 팔로잉 불러오기 구조분해 할당 부분의 ${}코드가 자꾸 깨져서 이 부분만 사진으로 올리겠습니다..!// 실제로 주소 가져오기 : 데이터를 가져오는 API를 호출하는 fetcher 함수 const fetcher = (url) => axios.get(url, { withCredentials: true }).then((result) => result.data ); // 프로필 컴포넌트(사용자 정의 태그) const Profile = () => { // 프로필 페이지에서 로그아웃한 상태일(me가 없을 때)때 메인 페이지로 이동 useEffect(() => { if (!(me && me.id)) { Router.push('/'); } }, [me && me.id]); // 팔로워 목록 더 불러오기 콜백 함수 const loadMoreFollowers = useCallback(() => { setFollowersLimit((prev) => prev + 3); // 기존 limit보다 3 올려주기 }, []); // 팔로잉 목록 더 불러오기 콜백 함수 const loadMoreFollowings = useCallback(() => { setFollowingsLimit((prev) => prev + 3); // 기존 limit보다 3 올려주기 }, []); // 로그인 하지 않은 상태일(me가 없을)때 프로필 페이지로 이동 막기 if (!me) { return '내 정보 로딩중...'; }; /* 팔로워 에러 or 팔로잉 에러 둘 중 하나가 에러났을 때 에러 콘솔하기 */ // Hook은 항상 모두 다 실행되어야 하기에 return은 Hook보다 위에 위치할 수 없다. if (followerError || followingError) { console.error(followerError || followingError); return <div>팔로워/팔로잉 로딩 중 에러가 발생합니다.</div>; } return ( <> <Head> <title>내 프로필 | NodeBird</title> </Head> <AppLayout> {/* ---------- 닉네임 수정 폼 ---------- */} <NicknameEditForm /> {/* ---------- 팔로잉 목록 ---------- */} console.log(followingsData); <FollowList header="팔로잉" data={followingsData} onClickMore={loadMoreFollowings} // SWR에서의 로딩 : 팔로잉 데이터와 팔로잉 에러가 없을 때 loading={!followingsData && !followingError} /> {/* ---------- 팔로워 목록 ---------- */} <FollowList header="팔로워" data={followersData} onClickMore={loadMoreFollowers} // SWR에서의 로딩 : 팔로워 데이터와 팔로워 에러가 없을 때 loading={!followersData && !followerError} /> </AppLayout> </> ); };user.js(백엔드 팔로워, 팔로잉 라우터)// 팔로워 라우터 router.get('/followers', isLoggedIn, async (req, res, next) => { // GET /user/followers try { /* 나를 찾는 함수 */ const user = await User.findOne({ where: { id: req.user.id }}); /* ---------- 만약 내가 없다면 400번대 에러 출력 ---------- */ if (!user) { res.status(403).send('없는 사람을 찾으려고 하시네요?'); } /* 사용자 팔로워 목록 가져오기 */ // limit을 올려주면 그 limit만큼 더 가져오도록 하기 const followers = await user.getFollowers({ limit: parseInt(req.query.limit, 10), }); /* 팔로워 목록을 프론트로 넘기기 */ res.status(200).json(followers); /* ---------- 에러 캐치 ---------- */ } catch (error) { console.error(error); next(error); } }); // 팔로잉 라우터 router.get('/followings', isLoggedIn, async (req, res, next) => { // GET /user/followings try { /* 나를 찾는 함수 */ const user = await User.findOne({ where: { id: req.user.id }}); /* ---------- 만약 내가 없다면 400번대 에러 출력 ---------- */ if (!user) { res.status(403).send('없는 사람을 찾으려고 하시네요?'); } /* 사용자 팔로잉 목록 가져오기 */ // limit을 올려주면 그 limit만큼 더 가져오도록 하기 const followings = await user.getFollowings({ limit: parseInt(req.query.limit, 10), }); /* 팔로잉 목록을 프론트로 넘기기 */ res.status(200).json(followings); /* ---------- 에러 캐치 ---------- */ } catch (error) { console.error(error); next(error); } });FollowList.js// 팔로우 리스트 컴포넌트(사용자 정의 태그) const FollowList = ({ header, data, onClickMore, loading }) => { . . . return ( <List /* 더보기 버튼 */ loadMore={( <div style={{ textAlign: 'center', margin: '10px 0px' }}> <Button onClick={onClickMore} loading={loading}>더 보기</Button> </div> )} . . . /> ); };
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
isLoggedIn추가 후 로그아웃 안되는 문제 발생
안녕하세요 선생님상황)req.logout안에 콜백 함수넣어서 로그아웃이 잘 되고 있었는데, isLoggedIn추가 후 상태코드 401이 뜨고 preview에는 로그인이 필요합니다가 뜨며 로그아웃 안되는 상황입니다. network로그인 했을 때로그아웃 했을 때network로그아웃 했을 때preview redux로그인 했을 때로그아웃 했을 때시도해본것)로그인 후 세션정보 콘솔 출력router.post('/login', isNotLoggedIn, (req, res, next)=> { passport.authenticate('local',(err, user, info) => { if(err) { console.error(err); return next(err); } if(info) { return res.status(401).send(info.reason); } return req.login(user,async(loginErr)=> { if(loginErr) { console.error(loginErr); return next(loginErr); } console.log('로그인 후 세션 정보:', req.session); //생략 }); })(req, res, next); }); //POST /user/loginuser.js의 logout에서 에러 발생시 출력 => 출력 xrouter.post('/logout', isLoggedIn, (req, res) => { req.logout((err) => { if (err) { console.error(err); return res.status(500).send('로그아웃 중 오류가 발생했습니다.'); } res.send('ok'); req.session.destroy(); }); });Middlewares에서 req.isAuthenticated()확인 => 결과 falseexports.isLoggedIn = (req, res, next) => { console.log('로그인 상태 확인:', req.isAuthenticated()); if(req.isAuthenticated()) { next(); } else { res.status(401).send('로그인이 필요합니다.'); } }질문)req.isAuthenticated가 false로 나와서 로그아웃이 안되는데 원인과 해결방법이 궁금합니다. 혹시 로그인하면 이것을 true가 되게 바꾸는 방법이 있나요?req.isAuthenticated()작성한 코드) UserProfileimport React, {useCallback} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {Card, Avatar, Button} from 'antd'; import styled from 'styled-components'; import {logoutRequestAction} from '../reducers/user'; const ButtonWrapper = styled(Button)` display: block; margin-left: auto; margin-right: auto; ` const UserProfile = () => { const dispatch = useDispatch(); const { me, logOutLoading } = useSelector((state) => state.user); const onLogout = useCallback(()=>{ dispatch(logoutRequestAction()); }, []); return ( //생략 <ButtonWrapper onClick={onLogout} loading={logOutLoading}>로그아웃</ButtonWrapper> ); } export default UserProfile;reducers/userimport {produce} from 'immer'; export const initialState = { logOutLoading: false,//logout시도중 logOutDone: false, logOutError: null, } export const LOG_OUT_REQUEST = 'LOG_OUT_REQUEST'; export const LOG_OUT_SUCCESS = 'LOG_OUT_SUCCESS'; export const LOG_OUT_FAILURE = 'LOG_OUT_FAILURE'; export const logoutRequestAction = () => { return { type: LOG_OUT_REQUEST } } const reducer = (state = initialState, action) => produce(state, (draft) => { switch(action.type){ case LOG_OUT_REQUEST: draft.logOutLoading = true; draft.logOutDone = false; draft.logOutError = null; break; case LOG_OUT_SUCCESS: draft.logOutLoading = false; draft.logOutDone = true; draft.me = null; break; case LOG_OUT_FAILURE: draft.logOutLoading = false; draft.logOutError = action.error; break; default: break; } }); export default reducer;sagas/userimport axios from 'axios'; import { all, call, delay, fork, put, takeLatest } from 'redux-saga/effects'; import { LOG_OUT_FAILURE, LOG_OUT_REQUEST, LOG_OUT_SUCCESS, } from '../reducers/user'; function logOutAPI(){ return axios.post('/user/logout'); } function* logOut() { try{ yield call(logOutAPI); yield put({ type: LOG_OUT_SUCCESS, }); } catch(err) { yield put({ type: LOG_OUT_FAILURE, error: err.response.data }); } } function* watchLogOut(){ yield takeLatest(LOG_OUT_REQUEST, logOut); } export default function* userSaga() { yield all ([ fork(watchLogOut), ]) }routes/user.jsconst express = require('express'); const bcrypt = require('bcrypt'); const {User, Post} = require('../models'); const router = express.Router(); const passport = require('passport'); const {isLoggedIn, isNotLoggedIn} = require('./middlewares'); router.post('/login', isNotLoggedIn, (req, res, next)=> { passport.authenticate('local',(err, user, info) => { if(err) { console.error(err); return next(err); } if(info) { return res.status(401).send(info.reason); } return req.login(user,async(loginErr)=> { if(loginErr) { console.error(loginErr); return next(loginErr); } const fullUserWithoutPassword = await User.findOne({ where: {id: user.id}, attributes:{ exclude:['password'] }, include: [{ model: Post }, { model: User, as:'Followings', }, { model: User, as:'Followers' }] }) return res.status(200).json(fullUserWithoutPassword); }); })(req, res, next); }); //POST /user/login //생략 const {isLoggedIn, isNotLoggedIn} = require('./middlewares'); router.post('/logout', isLoggedIn, (req, res) => { req.logout(() => { req.session.destroy(); res.send('ok'); }); });routes/middlewaresexports.isLoggedIn = (req, res, next) => { if(req.isAuthenticated()) { next(); } else { res.status(401).send('로그인이 필요합니다.'); } } exports.isNotLoggedIn = (req, res, next) => { if(!req.isAuthenticated()){ next(); } else { res.status(401).send('로그인 하지 않은 사용자만 접근이 가능합니다.'); } }passport/indexconst passport = require('passport'); const local = require('./local'); const { User } = require('../models'); module.exports = () => { passport.serializeUser((user,done) => { done(null,user.id);//첫번째 인자 에러, 두번째 인자 성공(쿠키와 묶어줄 user아이디만 저장) }); passport.deserializeUser(async(id, done) => { try { const user = await User.findOne({where:{id}}) done(null,user); } catch(error) { console.error(error); done(error); } }); local(); };passport/localconst passport = require('passport'); const {Strategy:LocalStrategy} = require('passport-local'); const bcrypt = require('bcrypt'); const {User} = require('../models'); const express = require('express'); const router = express.Router(); router.post('/login', passport.authenticate('local')); module.exports = () => { passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password', }, async(email, password, done) => { try { const user = await User.findOne({ where:{email} }); if(!user) { return done(null, false, {reasone: '존재하지 않는 이메일입니다!'}) } const result = await bcrypt.compare(password, user.password); if(result) { return done(null, user);//성공에 사용자 정보 넘겨줌 } return done(null, false, {reason:'비밀번호가 틀렸습니다.'}); } catch(error) { return done(error); } })); } 사용 하는 OS )mac설치 버전)back{ "name": "react-nodebird-back", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "luckyhaejin", "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", "express-session": "^1.17.3", "mysql2": "^3.6.5", "passport": "^0.7.0", "passport-local": "^1.0.0", "sequelize": "^6.35.2", "sequelize-cli": "^6.6.2" }, "devDependencies": { "nodemon": "^2.0.22" } }
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
Error: Too many keys specified; max 64 keys allowed (인덱스 관련 에러)
안녕하세요 선생님 상황)백엔드 서버 실행시 인덱스를 64개까지 만들 수 있다는 에러가 발생했습니다. 시도해본 것)인덱스 삭제 명령어를 찾아보았는데 인덱스 명을 붙여줘야해서 한개씩 지울 수 있는 상황입니다.alter table `Users` drop index `email_15` 질문) 혹시 Users테이블에 email 컬럼에 대한 인덱스 전체를 한번에 지울 수 있는 방법이 있을까요?
-
미해결처음 만난 리덕스(Redux)
counter 예제에서 script로 redux를 CDN으로 불러올 때
counter 예제에서 script로 redux를 CDN으로 불러올 때404가 나는 것 같습니다!<script src=">"https://unpkg.com/browse/redux@5.0.1/dist/redux.mjs"></script>위와 같이 수정했는데 Redux를 찾을 수 없다고 나오네요..!혹시 뭐가 문제일까요 ?
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
포스트맨 styled component 서버사이드 렌더링이 잘 이루어졌는지 궁금합니다!
안녕하세요 제로초님! 항상 강의 잘 보고 있고 질문에 답해주셔서 감사합니다!섹션5 Next.js 서버사이드렌더링 CSS 서버사이드렌더링 강의 끝까지 진행한 수강생 입니다!해당 강의 영상의 2분 45초에서 styled component 진짜로 서버사이드 렌더링 하려면포스트맨에서 게시글에 대한 주소 입력 후 send를 눌렀을 때 화면에 styled component가 떠야 한다고 말씀하셨습니다!send 후 아래의 화면과 같이 뜨는데 CSS 서버사이드 렌더링이 제대로 된 게 맞는 건지 궁금합니다!++++++추가 질문입니다..!<NextScript /> 위에 <script src="https://polyfill.io/v3/polyfill.min.js?......" /> 처럼 코드를 넣지 않아도 되나요?아래 질문 글의 작성자님처럼 io부분을 넣지 않아도 코드가 정상적으로 작동되기에 질문 드립니다!https://www.inflearn.com/course/lecture?courseSlug=%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC&unitId=48856&category=questionDetail&tab=community&q=128118++++++++++
-
미해결따라하며 배우는 리액트 네이티브 기초
createStore를 통해 middleware를 등록할 때 오류 (5.0.1 버전)
const store = createStore(rootReducer, middleware);강의와 같이 위처럼 코드를 작성할 시 에러가 발생합니다. 버전은 5.0.1 버전입니다.공식 문서에서 createStore 부분을 보면 reducer와 enhancer이외에 preloadedState라는 값이 있는데 해당 값을 넣어주니 정상적으로 작동했습니다.const preloadedState = { counter: 0, }; const store = createStore(rootReducer, preloadedState, middleware);preloadedState는 선택적으로 사용할 수 있다고 되어 있는데 왜 사용하지 않으면 에러가 발생하는지 궁금합니다.또한 preloadedState는 store의 초기 상태를 나타내고 있다고 하는데 todos의 초기 상태를 어떻게 설정해야할지 궁금합니다. (그냥 undefined로만 적어도 상관없을까요)
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 쇼핑몰 사이트 만들기[전체 리뉴얼]
리덕스안에 _id값
안녕하세요 (상품 업로드 페이지 기능 생성하기) 파트를 듣는중,상품 업로드 버튼을 마지막에 눌렀는데Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '_id')at handleSubmit (index.jsx:52:29)이런 내용이 나와서 리덕스를 확인해보니,user/loginUser/pending 그리고 fulfilled,user/authUser/pending 까지는state의 구조가 영상이랑 똑같이user>userData>user>_id 로 보여지는데user/authUser/fulfilled 가 된 상태의 리덕스 state를확인해보면user>userData>id(_id 아님)이렇게 보여서 일단오류안나게끔 body의 writer 값을 writer:userData._id로 고치니까 몽고디비에상품 데이터 통신은 되긴 하는데..(이것도 왜 ,, 오류가 안나는지 모르겠지만.. userData에는 값이 _id가 아닌 id로 보여지는데..)저는 왜 강사님과 다르게 그것도 authUser fulfilled된 이후에 리덕스 데이터가 저렇게 되는지 궁금합니다.의심되는 곳으로1)backend>src>routes>users.js에서 get request로 /auth 부분에 제가 뭘 잘못 타이핑했는지 다시 수업 돌려서 확인했으나 동일해 보이고..2)userSlice의 addCase를 authUser부분 잘못했나 싶어서 다시 수업 돌려서 확인했으나 동일한것 같습니다. 어딜 더 확인해 봐야할까요?
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
로그인 401 Error (routes에서 user false, 'Missing credentials')
안녕하세요 선생님 상황)쿠키/세션 전체 로그인 흐름 강좌를 따라해보았는데 로그인을 했을 때 401 에러가 발생하는 상황입니다.시도해본 것)아래 파일들의 로그인과 관련된 코드에 로그를 찍어보았고 이런 결과가 출력 되었습니다.1)LoginForm => onSubmitForm함수에서 email,password찍으면 이메일,비밀번호 출력됨2)reducers/user => loginRequestAction함수에서 data찍으면 이메일만 출력되고 loginAction함수에서는 출력안됨3)sagas/user => logIn함수에서 action.data에 이메일만 출력되고 result는 출력안됨4)user/routes => err는 null, user는 false, info는 { message: 'Missing credentials' }라고 출력됨5)passport/index, passport/local => 출력안됨질문1) reducer와 sagas에서는 원래 비밀번호가 출력이 안되는게 맞나요? 질문2) 만약에 reducer와 sagas에서 원래 비밀번호가 안나오는게 맞다면 어떤 부분에 문제가 있어서 reducer와 saga에서 data 나오는데, routes에서는 users가 false로 나오고 Missing credentials가 나오는건가요?질문3)이 문제를 어떻게 해결하면 좋을까요,,?작성한 코드) 글자수 제한이 있어 로그인 부분만 올립니닷,,!LoginFormimport React, { useCallback, useEffect } from 'react'; import {Form, Input, Button} from 'antd'; import Link from 'next/link'; import styled from 'styled-components'; import {useDispatch, useSelector} from 'react-redux'; import useInput from '../hooks/useInput'; import {loginRequestAction} from '../reducers/user'; const LoginForm = () => { const dispatch = useDispatch(); const {logInLoading, logInError} = useSelector((state) => state.user); const [email, onChangeEmail] = useInput(''); const [password, onChangePassword] = useInput(''); useEffect(() => { if (logInError) { alert(logInError); } }, [logInError]); const onSubmitForm = useCallback(() => { console.log('LoginForm에서 email, password', email, password); //email, password luckyhaejin1@naver.com 1234 dispatch(loginRequestAction(email, password)); },[email, password]); return ( <FormWrapper onFinish={onSubmitForm}> {/* 생략 */} <Button type="primary" htmlType="submit" loading={logInLoading}>로그인</Button> </FormWrapper> ); } reducers/userimport {produce} from 'immer'; export const initialState = { logInLoading: false, //login시도중 logInDone: false, logInError: null, //생략 loginData:{} } export const LOG_IN_REQUEST = 'LOG_IN_REQUEST'; export const LOG_IN_SUCCESS = 'LOG_IN_SUCCESS'; export const LOG_IN_FAILURE = 'LOG_IN_FAILURE'; export const loginAction = (data) => { console.log('reducers loginAction에서 data', data);//로그x return (dispatch) => { setTimeout(() => { dispatch(loginRequestAction()); }, 2000); dispatch(loginRequestAction()); } } export const loginRequestAction = (data) => { console.log('reducers loginRequestAction에서 data', data); //luckyhaejin1@naver.com return { type: LOG_IN_REQUEST, data } } const reducer = (state = initialState, action) => produce(state, (draft) => { switch(action.type){ case LOG_IN_REQUEST: draft.logInLoading = true; draft.logInError = null; draft.logInDone = false; break; case LOG_IN_SUCCESS: draft.logInLoading = false; draft.logInDone = true; draft.me = action.data; break; case LOG_IN_FAILURE: draft.logInLoading = false; draft.logInError = action.error; break; //생략 } }); export default reducer; sagas/userimport axios from 'axios'; import { all, call, delay, fork, put, takeLatest } from 'redux-saga/effects'; import { LOG_IN_FAILURE, LOG_IN_REQUEST, LOG_IN_SUCCESS, } from '../reducers/user'; function logInAPI(data){ return axios.post('/user/login', data); } function* logIn(action) { try { console.log('sagas에서 action.data', action.data);//luckyhaejin1@naver.com const result = yield call(logInAPI, action.data); console.log('sagas에서 logIn함수에서 result', result);//로그x yield put({ type: LOG_IN_SUCCESS, data: result.data, }); } catch(err) { console.error(err); yield put({ type: LOG_IN_FAILURE, data: err.response.data, }); } } function* watchLogIn() { yield takeLatest(LOG_IN_REQUEST, logIn); } export default function* userSaga() { yield all ([ fork(watchLogIn) ]) }routes/userconst express = require('express'); const bcrypt = require('bcrypt'); const {User} = require('../models'); const router = express.Router(); const passport = require('passport'); router.post('/login',(req, res, next)=> { console.log('routes 진입'); passport.authenticate('local',(err, user, info) => { if(err) { console.error(err); return next(err); } if(info) { console.log('routes err', err);//null console.log('routes user', user);//false console.log('routes info', info);//{ message: 'Missing credentials' } return res.status(401).send(info.reason); } return req.login(user,async(loginErr)=> { if(loginErr) { console.log('routes loginErr', loginErr); console.error(loginErr); return next(loginErr); } return res.status(200).json(user); }); })(req, res, next); }); //POST /user/login module.exports = router; passport/indexconst passport = require('passport'); const local = require('./local'); const { User } = require('../models'); module.exports = () => { passport.serializeUser((user,done) => { console.log('serializeUser의 user.id', user.id);//로그x done(null,user.id);//첫번째 인자 에러, 두번째 인자 성공(쿠키와 묶어줄 user아이디만 저장) }); passport.deserializeUser(async(id, done) => { try { const user = await User.findOne({where:{id}}) console.log('deserializeUser의 user', user);//로그x done(null,user); } catch(error) { console.error(error); done(error); } }); local(); };passport/localconst passport = require('passport'); const {Strategy:LocalStrategy} = require('passport-local'); const bcrypt = require('bcrypt'); const {User} = require('../models'); const express = require('express'); const router = express.Router(); router.post('/login', passport.authenticate('local')); module.exports = () => { passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password', }, async(email, password, done) => { console.log('Passport LocalStrategy - Start');//로그x try { const user = await User.findOne({ where:{email} }); if(!user) { console.log('Passport LocalStrategy - User not found');//로그x return done(null, false, {reasone: '존재하지 않는 이메일입니다!'}) } const result = await bcrypt.compare(password, user.password); if(result) { console.log('Passport LocalStrategy - Login success');//로그x return done(null, user);//성공에 사용자 정보 넘겨줌 } console.log('Passport LocalStrategy - Incorrect password');//로그x return done(null, false, {reason:'비밀번호가 틀렸습니다.'}); } catch(error) { console.error('Passport LocalStrategy - Error:', error);//로그x return done(error); } })); } 사용중인 OS) macOS Apple M1 Pro설치된 버전) back/Package.json "dependencies": { "passport": "^0.7.0", "passport-local": "^1.0.0", "sequelize": "^6.35.2", "sequelize-cli": "^6.6.2" },
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
next js 무한 렌더링 문제
import React, { useState } from "react"; const ToggleButtons = () => { const [buttonAActive, setButtonAActive] = useState(false); const [buttonBActive, setButtonBActive] = useState(false); const handleButtonClick = (button) => { if (button === "A") { setButtonAActive((prev) => !prev); setButtonBActive(false); } else if (button === "B") { setButtonBActive((prev) => !prev); setButtonAActive(false); } }; return ( <div> <button onClick={() => handleButtonClick("A")} style={{ backgroundColor: buttonAActive ? "green" : "white" }} > Button A </button> <button onClick={() => handleButtonClick("B")} style={{ backgroundColor: buttonBActive ? "red" : "white" }} > Button B </button> </div> ); };next js로 버튼 A와 B가 있는데 A가 켜져있는 상태에서 B를 누르면 A가 꺼지고 B가 켜져있는 상태에서 A를 누르면 꺼지고 A가 켜져있는 상태에서 A를 또 누르면 꺼지고 B를 누른 상태에서 B를 또 누르면 꺼지는 버튼 2개를 만들고 있는데 이렇게 코드를 작성하니까 무한 렌더링이 걸리는데 해결 방법이 있을까요?
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
리트윗 라우터에서 설정한 리트윗 검사가 되지 않습니다!(중복 리트윗 체크 및 본인 게시글 리트윗이 막아지지 않는 문제)
안녕하세요! React로 NodeBird SNS 만들기섹션4 리트윗하기 강의 시청 중 발생한 에러에 대해 질문 드립니다!항상 강의 잘 보고 있습니다! 제로초님 감사합니다! [리트윗 라우터에서 설정한 리트윗 검사]1. 자기 게시글을 리트윗한 경우2. 자기 게시글을 리트윗한 다른 게시글을 다시 자기가 리트윗한 경우3. 이미 리트윗한 게시글을 또 리트윗 하는 경우(중복 리트윗) 여러 개의 게시글을 가져오는 라우터에서 리트윗한 게시글, 작성자, 이미지 모델을 넣었으며,게시글 Reducer에서 리트윗 실패 시 실패 확인을 'draft.retweetError = action.error;'로 확인하였습니다.게시글 Saga에서도 리트윗 요청 실패 시 실패 결과를 'error: err.response.data'로 설정했습니다.★ 프론트, 백엔드 쪽 터미널과 콘솔, 리덕스, 네티워크 쪽에 오류가 없음을 확인하였습니다.코드 오타가 원인이라고 파악해 제로초님의 깃허브와 제 코드를 비교하며 확인했으나원인을 찾을 수 없어 질문 올립니다. 아래는 가장 의심되는 코드 입니다.중요하지 않은 코드는 '. . .' 으로 생략하였습니다.front/pages/index.js // React 라이브러리 훅 불러오기 import React, { useEffect } from 'react'; . . . // 홈 컴포넌트(사용자 정의 태그) const Home = () => { const dispatch = useDispatch(); const { me } = useSelector((state) => state.user); const { mainPosts, hasMorePosts, loadPostsLoading, retweetError } = useSelector((state) => state.post); . . . // 리트윗 실패 시 리트윗 에러 alert 창 띄우기 useEffect(() => { if (retweetError) { alert(retweetError); } }, [retweetError]);front/components/PostCard.js따로 게시글을 리트윗 실패(RETWEET_FAILURE) 액션을 디스패치 해야 하는지 의심이 들었습니다!// 게시글 카드 컴포넌트(사용자 정의 태그) const PostCard = ({ post }) => { . . . // 리트윗 버튼 콜백 함수 const onRetweet = useCallback(() => { // 로그인을 안했을 때 '로그인이 필요합니다.' alert 창 띄우기 if (!id) { return alert('로그인이 필요합니다.'); } /* 리트윗 요청 액션 객체 디스패치 */ return dispatch({ type: RETWEET_REQUEST, // 리트윗 요청 액션 data: post.id, // 게시글 아이디 }); }, [id]); . . . return ( <div style={{ marginBottom: '20px' }}> <Card /* ---------- 이미지 : 이미지는 1개 이상 ---------- */ cover={post.Images[0] && <PostImages images={post.Images} />} /* ---------- 액션 버튼 ---------- */ actions={[ /* ---------- 리트윗 버튼 ---------- */ <RetweetOutlined key="retweet" onClick={onRetweet} />, . . . ]} /* 카드 제목 */ title={post.RetweetId // 리트윗 게시글이면 게시글 사용자 닉네임님이 리트윗 하셨습니다. 제목 써주기 ? `${post.User.nickname}님이 리트윗 하셨습니다.` // 일반 게시글이면 제목 안 써주기 : null } . . . > {/* Card 닫기 */} {/* ---------- 리트윗 게시글 ---------- */} {post.RetweetId && post.Retweet ? ( <Card /* ---------- 이미지 : 이미지는 1개 이상 ---------- */ cover={post.Retweet.Images[0] && <PostImages images={post.Retweet.Images} />} > <Card.Meta // 메인 게시글 리트윗한 사용자 닉네임의 첫 번째 글자를 // 아바타 아이콘으로 표시 avatar={<Avatar>{post.Retweet.User.nickname[0]}</Avatar>} // 메인 게시글 리트윗한 게시글 작성자 이름 title={post.Retweet.User.nickname} // 메인 게시글 게시글 콘텐츠 description={<PostCardContent postData={post.Retweet.content} />} /> </Card> ) : ( /* ---------- (리트윗을 하지않은) 일반 게시글 ---------- */ <Card.Meta // 메인 게시글 사용자 닉네임의 첫 번째 글자를 아바타 아이콘으로 표시 avatar={<Avatar>{post.User.nickname[0]}</Avatar>} // 메인 게시글 작성자 이름 title={post.User.nickname} // 메인 게시글 콘텐츠 description={<PostCardContent postData={post.content} />} /> )} </Card> back/routes/post.js// 리트윗 라우터 router.post('/:postId/retweet', isLoggedIn, async (req, res, next) => { // POST /post/동적 히든/retweet try { /* 존재하지 않는 게시글이 있는지 검사하는 함수 */ const post = await Post.findOne({ where: { id: req.params.postId }, // 모델 가져오기 include: [{ model: Post, as: 'Retweet', // as: 'Retweet'으로 include를 해주면 post.retweet이 생긴다. }], }); /* ---------- 만약 존재하지 않는 게시글이 있다면 400번대 에러 출력 ---------- */ if (!post) { return res.status(403).send('존재하지 않는 게시글입니다.'); } /* 자기 게시글을 리트윗하기 or 자기 게시글을 리트윗한 다른 게시글을 다시 자기가 리트윗하기 막기 */ if (req.user.id === post.UserId || (post.Retweet && post.Retweet.UserId === req.user.id)) { return res.status(403).send('자신의 글은 리트윗할 수 없습니다.'); } // 리트윗할 Id : 리트윗한 게시글이면 리트윗 아이디 사용 or 아니면 게시글 아이디 사용 const retweetTargetId = post.RetweetId || post.id; /* 이미 리트윗한 게시글을 또 리트윗하는지 검사하는 함수(두 번 리트윗 막기) */ const exPost = await Post.findOne({ where: { UserId: req.user.id, RetweetId: retweetTargetId, }, }); /* ----- 만약 이미 리트윗한 게시글을 또 리트윗한다면 400번대 에러 출력 ----- */ if (exPost) { return res.status(403).send('이미 리트윗했습니다.'); } /* await : 실제로 데이터가 들어감, create : 테이블 안에 데이터를 넣음 */ const retweet = await Post.create({ UserId: req.user.id, RetweetId: retweetTargetId, content: 'retweet', // 게시글 모델에서 allowNull을 false로 설정했기 때문에 게시글 콘텐츠가 필수다. }); /* ---------- 내가 어떤 게시글을 리트윗했는지 찾는 함수 ---------- */ const retweetWithPrevPost = await Post.findOne({ where: { id: retweet.id }, // 모델 가져오기 include: [{ /* ---------- 리트윗한 게시글 ---------- */ model: Post, as: 'Retweet', // 리트윗한 게시글이 post.Retweet으로 담긴다. // 모델 가져오기 include: [{ /* ---------- 리트윗한 게시글의 작성자 ---------- */ model: User, attributes: ['id', 'nickname'], // id, nickname 데이터만 가져오기 }, { /* ---------- 리트윗한 게시글의 이미지 ---------- */ model: Image, }] }, { /* ---------- 게시글 작성자 ---------- */ model: User, attributes: ['id', 'nickname'], // id, nickname 데이터만 가져오기 }, { /* ---------- 게시글 좋아요 누른 사람들 ---------- */ model: User, as: 'Likers', attributes: ['id'], // id 데이터만 가져오기 }, { /* ---------- 게시글 이미지 ---------- */ model: Image, }, { /* ---------- 게시글 답글 ---------- */ model: Comment, // 모델 가져오기 include: [{ /* ---------- 게시글 답글의 작성자 ---------- */ model: User, attributes: ['id', 'nickname'], // id, nickname 데이터만 가져오기 }], }], }); /* 게시글 작성 성공 시 어떤 게시글을 리트윗 했는지에 대한 정보를 프론트로 돌려주기 */ res.status(201).json(retweetWithPrevPost); /* ---------- 에러 캐치 ---------- */ } catch (error) { console.error(error); next(error); } }); +++ 줄바꿈이 되지 않은 문제를 수정하였습니다.
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
인피니트 스크롤링 적용시 LOAD_POST_REQUEST 두번 찍히는 문제
안녕하세요 선생님 상황)인피니트 스크롤링 적용시 LOAD_POST_REQUEST 두번 찍히는 상황인데 이거의 원인과 해결방법을 어떻게 찾을 수 있을까요? loadPostsLoading과 throttle을 적용했는데도 2번씩 실행되는 상황입니다.redux) 작성한 코드) 10,000 자이하만 적을 수 있어서 LOAD_POSTS_REQUEST 관련 코드만 올립니다..!!pages/index.jsimport React, { useEffect } from 'react'; import {useDispatch, useSelector} from 'react-redux'; import AppLayout from '../components/AppLayout'; import PostCard from '../components/PostCard'; import PostForm from '../components/PostForm'; import {LOAD_POSTS_REQUEST} from '../reducers/post'; const Home = () => { const dispatch = useDispatch(); const { me } = useSelector((state) => state.user); const { mainPosts, hasMorePosts, loadPostsLoading } = useSelector((state) => state.post); useEffect(() => { 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]); return ( <AppLayout> {me && <PostForm />} {mainPosts.map((post) => <PostCard key={post.id} post={post} />)} </AppLayout> ); }; export default Home;reducers/post.jsimport shortId from 'shortid'; import {produce} from 'immer'; import faker from 'faker'; export const initialState = { mainPosts:[], imagePaths: [], //게시물 저장 경로 hasMorePosts: true, loadPostsLoading: false, //게시글 로드 완료시 true loadPostsDone: false, loadPostsError: null, } export const generateDummyPost = (number) => Array(number).fill().map(() => ({ id: shortId.generate(), User: { id: shortId.generate(), nickname: faker.name.findName() }, content: faker.lorem.paragraph(), Images: [{ src: 'https://cdn.pixabay.com/photo/2017/07/25/01/22/cat-2536662_1280.jpg' //faker.image.imageUrl(640, 480, true), lorempixel.com 고장나서 임시로 }], Comments: [{ User: { id:shortId.generate(), nickname:faker.name.findName(), }, content:faker.lorem.sentence(), }], })); export const LOAD_POSTS_REQUEST = 'LOAD_POSTS_REQUEST'; export const LOAD_POSTS_SUCCESS = 'LOAD_POSTS_SUCCESS'; export const LOAD_POSTS_FAILURE = 'LOAD_POSTS_FAILURE'; const dummyPost = (data) => ({ id: data.id, content: data.content, User: { id:1, nickname:'해지니', }, Images: [], Comments: [], }); const dummyComment = (data) => ({ id: shortId.generate(), content: data, User: { id: 1, nickname: '제로초' }, }); const reducer = (state = initialState, action) => produce(state, (draft) => { switch(action.type){ 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; default: break; } }); export default reducer; sagas/post.jsimport { all, fork, takeLatest, put, delay, throttle } from 'redux-saga/effects'; import axios from 'axios'; import shortId from 'shortid'; import { LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, generateDummyPost, } from '../reducers/post'; function loadPostsAPI(data){ return axios.get('/api/post', data); } function* loadPosts(action) { try{ // const result = yield call(loadPostsAPI, action.data); yield delay(1000); yield put({ type: LOAD_POSTS_SUCCESS, data:generateDummyPost(10) }); } catch(err) { yield put({ type: LOAD_POSTS_FAILURE, data: err.response.data }); } } function* watchLoadPosts(){ yield throttle(5000, LOAD_POSTS_REQUEST, loadPosts); } export default function* postSaga() { yield all([ fork(watchLoadPosts) ]); }사용중인 OS) macOS (Apple M1 Pro)
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 쇼핑몰 사이트 만들기[전체 리뉴얼]
제품을 배열로 다시 재정리 해줄때 split 후에 map을 하는 이유
따라하며-배우는-노드-리액트-쇼핑몰 > 쇼핑 카트 페이지 데이터 가져오기 강의를 수강중에 있습니다.17:30 부근에 제품을 배열로 다시 재정리 해주는 걸 설명해주고 계시는데 split 후에 map을 하는 이유가 궁금합니다.값을 map 처리하기 전후로 찍어보니 값이 동일하게 나오는데 map 처리를 해주는 이유가 따로 있나 싶어서 여쭤봅니다.
-
해결됨따라하며 배우는 노드, 리액트 시리즈 - 쇼핑몰 사이트 만들기[전체 리뉴얼]
redux thunk 관련 질문있습니다.
안녕하세요! 항상 좋은 강의 만들어주셔서 감사합니다ㅎㅎㅎ 이번에 새로 업데이트 된 강의를 들으면서 redux 강의 부분을 듣고 있다 이전 john ahn님의 로그인 강의 예제에서 질문이 생겼습니다.john ahn님의 로그인 강의에서export function loginUser(dataToSubmit) { const request = axios.post('/api/users/login', dataToSubmit) .then(response => response.data) return { type: LOGIN_USER, payload: request } } 이런식으로 로그인을 한 적이 있습니다! 이 때는 이 방법이 redux-thunk를 사용한 방법인 줄 알았는데, 강의를 들어보니 이것은 redux-thunk를 이용하지 않은 방법이라는 것을 알게되었습니다. 근데, axios를 사용해서 비동기로 처리했는데도 제 기억에 login 강의는 잘 동작했었습니다.왜 잘 동작했는지 chat GPT를 통해 질문하니 답변으로 이것은 잘 동작하지 않는 코드이고 "이 코드에서 axios.post는 비동기 함수이고, .then(response => response.data)를 통해 HTTP 요청의 결과를 처리하고 있습니다. request 변수는 이제 비동기 요청이 성공적으로 완료되었을 때 응답 데이터를 갖게 됩니다. 그러나 여전히 request는 프로미스입니다. 프로미스는 비동기 작업이 완료될 때까지 "미래의 값"을 나타내는 객체입니다." 이런 답변을 해주었습니다. 제가 생각해도 이것이 맞는 말 같은데 redux-thunk를 사용하지 않아도 왜 잘 동작한걸까요?? 굳이 redux-thunk를 사용하지 않아도 돼는 건가요?? 아니면 이번이 특별한 경우인가요??
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
팔로잉, 팔로워 목록에 사용자 이름과 StopOutlined 아이콘이 화면에 표시되지 않는 문제 질문합니다!(에러 메시지x)
안녕하세요! React로 NodeBird SNS 만들기섹션4 팔로우/언팔로우 강의를 끝까지 수강한 수강생입니다!항상 강의 잘 보고 있습니다! 제로초님 감사합니다! 김블루 계정으로 핑크공주 계정을 팔로우 했을 때팔로잉, 팔로워 사용자의 이름과 금지 표시 아이콘이 목록 화면에 보이지 않습니다!(아이콘이 들어가는 위치에 다른 아이콘을 넣었을 때도 아이콘이 표시되지 않았습니다!)리덕스와 네트워크 탭을 확인했을 때 팔로워, 팔로잉 목록 불러오기는 성공했습니다.vsCode 터미널, 콘솔, 리덕스, 네트워크 항목에도 에러가 없으며앤트 디자인 아이콘 버전도 아이콘에 맞게 수정하고, item도 추가했지만 해결되지 않았습니다!구글 검색 및 제로초님 강의와 트위터 클론 깃허브를 확인 후에도 원인을 알 수 없어 질문 글 올립니다! [김블루 계정의 팔로잉 목록][핑크공주 계정의 팔로워 목록]FollowList.js// Ant Design 아이콘 불러오기 import { StopOutlined } from '@ant-design/icons'; ... return ( <List style={{ marginBottom: '20px' }} /* 격자 모양 */ grid={{ gutter: 4, xs: 2, md: 3 }} /* 목록 크기 */ size="small" /* 팔로잉 목록, 팔로워 목록 헤더 */ header={<div>{header}</div>} /* 더보기 버튼 */ loadMore={ <div style={{ textAlign: 'center', margin: '10px 0px' }}> <Button>더 보기</Button> </div> } /* 팔로잉 목록, 팔로워 목록 전체 테두리 */ bordered /* 목록용 데이터소스 : 팔로잉 목록, 팔로워 목록 더미데이터 배열 전달 */ dataSource={data} renderItem={(item) => { <List.Item style={{ marginTop: '20px' }}> <Card actions={[<StopOutlined key="stop" onClick={onCancel(item.id)} />]}> <Card.Meta description={item.nickname} /> </Card> </List.Item> }} />/* List 닫음 */ ); };
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
리덕스 툴킷에서 extraReducers에 HYDRATE설정 방법이 궁금합니다.
툴킷으로 작업을 해보려고 하고 있는데 faker를 써보려다가 제로초님 깃허브에 HYDRATE에 관련된 사항이 있어서 작업을 하려고 하고 잇엇는데 제로초님처럼 똑같이 타이핑을 햇는데 에러가 납니다.const postReducer = createSlice({ name: "postReducer", initialState, reducers: {}, // 청크를 쓸때 필요함 extraReducers: (builder) => builder // 클라이언트 상태를 초기화하는 역할 .addCase(HYDRATE, (state, action) => ({ ...state, ...action.payload.data, })), }); 여기서 에러가 나는 부분은...action.payload.data, 중 payload부분이고 에러 사항은 다음과 같이 나옵니다. 'Action<"__NEXT_REDUX_WRAPPER_HYDRATE__">' 형식에 'payload' 속성이 없습니다.next-redux-wrapper 의 버전은 아래와 같습니다. 혹시 "next-redux-wrapper": "^8.1.0",혹시 next-redux-wrapper로 리덕스 툴킷에 HYDRATE를 하는 방법이 달라진걸까요....