월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
content의 세부항목 관계 질문드리겠습니다.
안녕하세요 제로초님 현재 개인프로젝트를 하면서 노드버드 강의를 듣고있습니다.저는 post의 구조를 아래와 같이 구성했습니다.title : 글 제목desc : 글 설명image: 이미지content : 글본문 (ingredient, recipes, tips 3가지로 구성)tags : 태그 이러한 경우에 content를 구성하는 ingredient, recipes, tips를 개별 테이블로 분리하는게 좋을까요?아니면 post라는 테이블에 위의 필드를 모두 포함하는게 좋을까요?DB를 이렇게 배워본적이 없어서 질문드리겠습니다.바쁘시겠지만 답변해주시면 감사하겠습니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
mysql workbench에서 데이터 수정 질문
안녕하세요 제로초님 강의 정말 잘 듣고있습니다.강의를 들으면서 아래와 같이 contene라는 하나의 컬럼으로 구성된 post.js의 db를 생성했습니다.const DataTypes = require('sequelize'); const { Model } = DataTypes; module.exports = class Post extends Model { static init(sequelize) { return super.init({ content: { type: DataTypes.TEXT, allowNull: false, }, }, { modelName: 'Post', tableName: 'posts', charset: 'utf8mb4', collate: 'utf8mb4_general_ci', sequelize, }); } static associate(db) { db.Post.belongsTo(db.User); db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' }); db.Post.hasMany(db.Comment); db.Post.hasMany(db.Image); db.Post.belongsToMany(db.User, { through: 'Like', as: 'Likers' }) } };다음 상황에서 다음과 같은 질문이 있습니다.이후에 content컬럼을 삭제하고 다른 컬럼을 추가하고 싶어서 workbench에서 sql문을 작성해서 삭제했는데 시퀄라이즈의 코드는 그대로 냅둬야하는건가요? 또한 workbench에서 작성하여 변경된 사항이 서버에도 자동으로 반영이 되는건가요? 만약 반영이된다면 마이그레이션으로 db를 수정하는것과 workbench에서 sql문으로 db를 수정하는방법의 차이점은 어떤건가요? 바쁘시겠지만 답변해주시면 정말 감사하겠습니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
에러 해결을 모르겠습니다
Server ErrorTypeError: Cannot read properties of undefined (reading 'user')This error happened while generating the page. Any console logs will be displayed in the terminal window.이런 에러 떠서 찾다가reducers폴더 안에 index.js 코드const initialState = { user: { isLoggedIn: false, user: null, signUpData: {}, loginData: {} }, post: { mainPosts: [], } }; export const loginAction = (data) => { return { type: 'LOG_IN', data, } } export const logoutAction = (data) => { return { type: 'LOG_OUT', } } // (이전상태, 액션) => 다음상태 const rootReducer = (state = initialState, action) => { switch (action.type) { case 'LOG_IN': return { ...state, user: { ...state.user, isLoggedIn: true, user: action.data, }, }; case 'LOG_OUT': return { ...state, user: { ...state.user, isLoggedIn: false, user: null, }, }; default: return { ...state } } }; export default rootReducer;default 넣었더니 새로운 에러 나왔습니다. LoginFrom.js 쪽인거 같은데 뭐가 문제인지 모르겠습니다..hooks안에 useInput.js 코드import {useState, useCallback} from 'react'; export default (initialValue = null) => { const [value, setValue] = useState(initialValue); const handler = useCallback((e) => { setValue(e.target.value); }, []); return [value, handler]; } LoginForm.js 코드import React, {useCallback, useMemo} from 'react'; import {Form, Input, Button} from 'antd'; import Link from 'next/link'; import styled from 'styled-components'; import {useDispatch} from 'react-redux'; import { loginAction } from '../reducers'; const ButtonWrapper = styled.div` margin-tap : 10px; `; const FormWrapper = styled(Form)` padding: 10px ` const LoginForm = () => { const dispatch = useDispatch(); const [id, onChangeId] = useInput(''); const [password, onChangePassword] = useInput(''); const onSubmitForm = useCallback(() => { console.log(id, password); dispatch(loginAction({id, password})); }, [id, password]); const style = useMemo(() => ({marginTop: 10}), []); return ( <FormWrapper onFinish={onSubmitForm}> <div> <label htmlFor='user-id'>아이디</label> <br/> <Input name='user-id' value={id} onChange={onChangeId} required /> </div> <div> <label htmlFor='user-password'>비밀번호</label> <br /> <Input name='user-password' type='password' value={password} onChange={onChangePassword} required /> </div> <ButtonWrapper style={style}> <Button type='primary' htmlType='submit' loading={false}>로그인</Button> <Link href="/signup"><a><Button>회원가입</Button></a></Link> </ButtonWrapper> </FormWrapper> ) } export default LoginForm; 버전 "dependencies": { "@ant-design/icons": "^4.7.0", "antd": "^4.23.1", "next": "^12.3.0", "next-redux-wrapper": "^6.0.2", "react-redux": "^8.0.2", "redux": "^4.2.0", "styled-components": "^5.3.5", "update": "^0.7.4" }, 어디가 문제인거죠.. 강의는 리덕스 구현하기 입니다.강의 잘 듣고 있는데 오류가 힘드네요 ㅠㅠ
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
이런 에러는 무슨에러에요??
경고 에러가 많은데 동작은 아직까지는 됩니다props 에러? antd 에러? AppLayout.js에 Menu.item 이부분이 문제인가요??AppLayout.propTypes = { children: PropTypes.node.isRequired, };여기쪽인가여..? key값?className 빠뜨렷는지..? 경고들 무시해도 되는건가요?? 동작은 하는데..아니면 해결해야 하나요??
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
라이브러리 공식문서에 대한 질문
안녕하세요 제로초님.어제 오픈채팅방에서 다음과 같은 질문을 드렸었습니다. 마침 제로초님이 답변을 주셨습니다. (답변주셔서 정말 감사했습니다.)실은 저도 redux toolkit 공식문서를 읽어보고 질문을 드렸지만, 제로초님이 키워드를 주신 부분에 대한 정보를 찾지 못했습니다.만약에 제로초 같은 분이 힌트라도 주시지 않았다면 저 문제를 해결하기 힘들었을 것 같습니다. 제로초님 께서는 저러한 지식을 얻으실때 어떻게 얻으셨는지 궁금합니다. 공식문서를 한번 정독을 하셨나요?저러한 정보가 어디에 위치했는지 파악을 하려면 한번 읽어보지 않고서는 안될 것 같다는 생각이 들었습니다.문서를 한번 읽어본다면 시간이 꽤 걸리는 과정일텐데, (제실력으로는 며칠이 걸릴 수 도 있겠네요.) 이러한 과정이 필연적으로 있어야 하는데 제가 시간이 걸린다는 핑계로 두고 하지 않은게 아닌지 제로초님께 의견을 여쭙고 싶습니다.redux toolkit 을 사용하려면서 공식문서를 한번도 읽지 않고 대충 사용하는 방법만 익히려고 제 자신을 반성하게 됩니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
HTTPS에 대해 질문이 있습니다.
제가 http -> https로 변경하기를 원합니다.무조건 도메인 주소를 사서 해야하나요?localhost로도 변경이 가능하다면 어떻게 해야하나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
req. 질문드립니다.
router.get('/login', async (req, res, next) => { try { if (req.user) { const user = await User.findOne({ where: { id: req.User.id } }) res.status(200).json(user); } else { res.status(200).json(null); } } catch (error) { console.error(error) next(error) } })선생님 여기서 req.user로 오는 값들이 어디서 넘어오는건가요 지금 앞단에서 오는 user인건가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
https에서 쿠키관련 질문이 있습니다.
안녕하세요. 제가 구름 ide를 통해 github react-nodebird의 tool-kit을 실행시켜봤는데, 쿠키가 전달이 잘 안되는 것 같습니다. getServerSideProps로는 loadMyInfo가 잘 실행되지 않고 loadPosts만 정상 작동하며, loadMyInfo는 useEffect안에 넣었을 때만 로그인 이후에 새로고침을 했을 경우 내 정보를 정상적으로 받을 수 있었습니다.제가 고민을 해본 결과 구름 ide에서 제공하는 도메인이 https라서 그러지 않을까 라고 생각했습니다. 만약 https라면 백엔드에서 코드를 어떻게 변경 시켜줘야하는지 알 수 있을까요? 현재 소스 코드는 react-nodebird toolkit 폴더와 backend, frontend 모두 동일합니다!
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
Hastag, Post 관계와 Post,Post관계에서 헷갈리는 점이 있습니다.
hastag와 post의 관계A해시태그를 작성한 게시물이 10개가 있으니일(해시태그)대 다(포스트) 관계이므로 db.Hastag.belongsToMany(db.Post); Post와 Post의 리트윗 관계A 포스트를 리트윗한 게시물이 10개가 있고,각각의 포스트는 하나의 메인 포스트 A 를 리트윗한거니 아래처럼 작성해야 하지 않을까? 라고 생각했습니다. db.Post.belongsToMany(db.Post,{ through:"MainPost"});db.Post.belongsTo(db.Post,{ through:"RetweetPost"});근대 제로초님께서는 일대일 관계로 작성해주셨는데 해시태그와 비슷한 경우인거같은데 둘이 다른 이유가 궁금합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
로그인 500 오류
const express = require('express') const bcrypt = require('bcrypt') const { User } = require('../models') const passport = require('passport') const router = express.Router(); router.post('/login', (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) } return res.status(200).json(user) }) })(req, res, next) }) router.post('/', async (req, res, next) => { try { const exUser = await User.findOne({ where: { email: req.body.email, } }); if (exUser) { return res.status(403).send('이미 사용 중인 아이디입니다.'); } const hashedPassword = await bcrypt.hash(req.body.password, 12); await User.create({ email: req.body.email, nickname: req.body.nick, password: hashedPassword, }); res.status(201).send('ok'); } catch (error) { console.error(error); next(error); // status 500 } }) router.get('/', (req, res, next) => { res.send('유저 페이지') }) module.exports = router;const passport = require('passport') const local = require('./local') const { User } = require('../models') module.exports = () => { passport.serializeUser((user, done) => { done(user.id) }) passport.deserializeUser(async (id, done) => { try{ const user = await User.findOne({ where: { id } }) done(null, user) }catch(error){ console.error(error) done(error) } }) local(); }const passport = require('passport') const { Strategy: LocalStrategy } = require('passport-local') const bcrypt = require('bcrypt') const { User } = require('../models') module.exports = () => { passport.use(new LocalStrategy({ usernameField: 'email', passwrodField: 'password', }, async (email, password, done) => { try { const user = await User.findOne({ where: { email } }) if (!user) { done(null, false, { reason: '존재하지 않는 이메일 입니다!' }) } const result = await bcrypt.compare(password, user.password) if (result) { return done(null, user) } return done(null, false, { reson: '비밀번호가 틀렸습니다.' }) } catch (error) { console.error(error); return done(error); } })); }const express = require('express'); const postRouter = require('./routes/post') const userRouter = require('./routes/user') const db = require('./models'); const { urlencoded } = require('express'); const app = express(); const cors = require('cors') const passportConfig = require('./passport'); const passport = require('passport'); const session = require('express-session') const cookieParser = require('cookie-parser') const dotenv = require('dotenv') dotenv.config(); db.sequelize.sync() .then(() => { console.log('db 연결성공') }) .catch(console.error) passportConfig(); app.use(cors({ origin: '*' })) app.use(express.json()) app.use(express.urlencoded({ extended: true })) app.use(cookieParser('nodebirdsecret')) app.use(session({ saveUninitialized: false, resave: false, secret: process.env.COOKIE_SECRET })) app.use(passport.initialize()) app.use(passport.session()); app.get('/', (req, res) => { res.send('hello express') }) app.get('/api', (req, res) => { res.send('hello express') }) app.get('/api/posts', (req, res) => { res.json([ { id: 1, content: 'hello' }, { id: 2, content: 'hello1' }, { id: 3, content: 'hello2' }, ]); }); app.use('/post', postRouter) app.use('/user', userRouter) app.listen(3065, () => { console.log('서버 실행중!!') });회원가입 로직은 잘 작동되는데 로그인기능이 안돼는 코드 한번 봐주실수 있으실까요? 201통신이 하나 오는건 또 무엇인지... 500으로 오류가 나오더라구요 그 다음에
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
React Query에 관해 질문드립니다
안녕하세요 제로초선생님 강의보고 열심히 공부하고 있습니다.새소식에 react-query로 변경하신 소스코드 깃헙 주소 올려주신걸 보고있는데구글링을 해보면 리액트쿼리는 서버데이터와 클라이언트 데이터를 따로 분리해서 서버데이터를 관리하기 위한 용도로 사용한다고 나오던데 리덕스같은 상태관리 라이브러리의 역할을 완전히 대체할 수 있는건가요혼자 프로젝트를 만드는 도중에 선생님이 next와 redux의 조합이 매끄럽지 않다고 하는점을 고려해서react-query(서버 데이터) 와 recoil(클라이언트 데이터)을 사용해서 상태관리와 비동기처리를 해보려고 하는데 리액트 쿼리만 쓰는것과 어떤 차이가 있을까요..?마땅히 이런 질문을 할 사람이 주변에 없어서 강의 내용과 외람된 질문 드려서 죄송합니다항상 강의 잘 듣고 있습니다 너무 감사합니다 !
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
강의에 대한 질문
리뉴얼 전 'React로 NodeBird SNS 만들기' 강의를 구매 후 리뉴얼 강의라고 되어 있어서 다시 구매했는데 이 강의도 업데이트 게시일이 2년 전이네요. 혹시 최신 내용을 다시 추가 리뉴얼로 올릴 계획이 있으신가요? 그럼 중복 구매를 해야해서.. 리뉴얼 전과 비교해 변경된 부분도 많은지 궁금합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
db sequelize 동기 처리 질문
db.sequelize.sync() .then(() => { console.log('db 연결성공') }) .catch(console.error)선생님 여기서 비동기처리하는 async가 아닌 동기 sync를 사용하 그후에 then을 사용하는 이유가 있을까요 보통 async하고 나서 then 처리하는 방식은 많이 봤는데요 문득 강의 진행중 궁금해서 질문드립니다. 선생님 추가로 회원가입 완료되면 초기화면 으로 돌아가는 로직 구현하는곳에서 문제가 있는데 useEffect(() => { if (signUpDone) { Router.push('/') } }, [signUpDone])회원가입 완료되면 signUpDone 이 ture로 변해서 이제 다시 회원가입 화면으로 접근이 안되는데 따로 또 액션 만들어 주어야하는건가요 선생님이 만드신 사이트는 정상 작동하던데 어떤 방식으로 구현하셨는지 궁금합니다. 아니면 강의 후반에 이 로직문제 해결하는게 나오나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
sudo certbot --nginx 중에 에러가 발생해 질문합니다.
안녕하세요. 제가 동영상 으로 진행하다가 404 에러가 발생하여댓글에 있는 포스트를 이용해서 진행하려고 했으나, sudo certbot --nginx 과정에서 에러가 발생하여 질문하고자 합니다.// etc/nginx/nginx.conf저는 localhost:3000으로 진행했어서 127.0.0.1:3000으로 기입했습니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
안녕하세요. 토큰 관련 질문입니다.
강사님 안녕하세요! nodeBird 강좌 너무 잘 듣고 있습니다.다름이 아니라, access token, refresh token 관련해서 궁금한 점이 생겨서 질문드립니다.access token, refresh token은 보통 실무에서 각각 어디에 저장을 해서 사용하나요? 만약 access token을 쿠키에 저장한다고 한다면, react-cookie 같은 라이브러리를 사용해서 프론트 단에서 구현하는 건가요? 아니면 백엔드 개발자와 협의를 해야하는 부분인가요? 헤더에 access token을 넣어서 서버와의 통신을 진행하다가, access token의 유효시간이 만료되면 자동으로 refresh token으로 교체가 되는건가요? 아니면 이러한 과정을 코드로 따로 구현해야 하는건가요?항상 유익한 강좌 감사합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
회원가입까지 잘 되는데, 로그인이 안됩니다.
안녕하세요 제로초님.매번 질문만 남겨드려 죄송합니다.매 작업마다 console을 찍어서 에러를 잡아내려고 하곤 있는데 회원가입에서도 문제가 없음에도 로그인이 이루어지지 않습니다.// routes/user.js const express = require("express"); const bcrypt = require("bcrypt"); const { User, Post } = require("../models"); // db 내에서 User 테이블을 가져온 것. const passport = require("passport"); const router = express.Router(); router.post("/", async (req, res, next) => { // POST /user || next를 넣으면 발생한 에러를 한방에 브라우저로 모아준다. try { const exUser = await User.findOne({ where: { email: req.body.email, }, }); if (exUser) { return res.status(403).send("이미 사용중인 아이디입니다."); // 여기서 보내는 res에 대한 send 메시지는 sagas/user.js의 signup 부분 err.response.data 가 된다! } const hashedPassword = await bcrypt.hash(req.body.password, 10); await User.create({ //! User 테이블 내에 post, 즉 생성을 요청한다. 또한 async await을 사용함으로써 비동기처리를 해주고 순서대로 처리될 수 있도록 해준다. email: req.body.email, nickname: req.body.nickname, password: hashedPassword, }); res.status(200).send("ok"); } catch (error) { console.error(error); next(error); // status 500 -> 서버쪽 에러라는 뜻 } }); // POST /user/login router.post("/login", (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); }); router.post("/logout", (req, res) => { req.logout(); req.session.destroy(); req.send("ok"); }); module.exports = router; db에도 문제가 없는 듯 한데.. 도대체 무엇을 놓치고 있는 것일까요..?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
무한 스크롤에 대해 질문드립니다.
무한 스크롤시, 데이터를 불러올 때마다 스크롤 위치가 다시 처음부터 시작되는데 이거 어떻게 해결해야 할까요?
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
해당 에러에 대한 원인에 대해 질문드리겠습니다.
최초 로그인, 로그아웃이후에 재로그인을 하면 아래와 같은 에러가 발생했습니다.구글링을 통해 문제는 아래와 같이 코드를 수정해서 해결했습니다.// 기존 코드 router.post('/logout', isLoggedIn, (req, res) => { req.logout(() => { res.redirect('/'); }); req.session.destroy(); res.send('OK'); }); // 해결 코드 router.post('/logout', isLoggedIn, (req, res) => { req.logout(() => { res.redirect('/'); }); }); 이 과정에서 문제를 올바르게 해결한건지, 그리고 어떻게 이런 문제가 발생한건지 이유에 대해서 알 수 있을까요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
concat, push map 사용시 오류에 관하여 질문
게시물 더미데이터 추가 될때 concat을 사용하던데 어차피 immer 통해서 불변성이 유지가 되는거라면 push를 사용해도 된다고 생각되어서 push 를 사용해봤더니 index.js에서 map부분에서 오류가 나던데 그 이유를 알 수 있을까요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
무한스크롤 request 호출문제
선생님 이번에 무한스크롤 구현하면서 지금 리퀘스트 요청이 여러번 발생하는걸 if문에 로딩시에만 if문이 발생하게 고쳐서 여러번 반복되게 하는걸 한번만 호출 할수있게 고치신걸로 아는데요 저도 분명히 고쳤는데 호출이 2번씩일어나는데 한번 봐주시면 감사하겠습니다. 코드 여러군데 봐도 왜 호출이 두번 일어나는지 모르겠네요 한번 봐주시면 감사하겠습니다.//index.js import React, { useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import AppLayout from '../components/AppLayout' import PostForm from '../components/PostForm' import PostCard from '../components/PostCard' import { LOAD_POST_REQUEST } from '../reducers/post' const Home = () => { const dispatch = useDispatch(); const { me } = useSelector((state) => state.user) const { mainPosts, hasMorePost, getPostLoadding } = useSelector((state) => state.post) useEffect(() => { dispatch({ type: LOAD_POST_REQUEST, }) }, []) useEffect(() => { function onScroll() { console.log(window.scrollY, document.documentElement.clientHeight, document.documentElement.scrollHeight) if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) { if(hasMorePost && !getPostLoadding){ dispatch({ type: LOAD_POST_REQUEST, }) } } } window.addEventListener('scroll', onScroll); return () => { window.removeEventListener('scroll', onScroll); } }, [hasMorePost, getPostLoadding]) return ( <AppLayout> {me && <PostForm />} {mainPosts.map((item) => <PostCard key={item.id} post={item} />)} </AppLayout> ) } export default Home /reducers/post.js import shortId from 'shortid'; import produce from 'immer'; import faker from 'faker'; export const initialState = { mainPosts: [{ id: 1, User: { id: 1, nickname: '제로초', }, content: '첫번째 게실글 #해시태그 #익스프레스', Images: [{ id: shortId.generate(), src: 'https://bookthumb-phinf.pstatic.net/cover/137/995/13799585.jpg?udate=20180726', }, { id: shortId.generate(), src: 'https://gimg.gilbut.co.kr/book/BN001958/rn_view_BN001958.jpg', }, { id: shortId.generate(), src: 'https://gimg.gilbut.co.kr/book/BN001998/rn_view_BN001998.jpg', }], Comments: [{ id: shortId.generate(), User: { id: shortId.generate(), nickname: 'nero', }, content: '우와우와' }, { id: shortId.generate(), User: { id: shortId.generate(), nickname: 'wi', }, content: '힘내자' }], }], imagePath: [], hasMorePost : true, getPostLoadding: false, getPostDone: false, getPostErr: null, addPostLoadding: false, addPostDone: false, addPostErr: null, removePostLoadding: false, removePostDone: false, removePostErr: null, addCommentLoadding: false, addCommentDone: false, addCommentErr: null, } export const getDemmuyPost = (number) => Array(number).fill().map(() => ({ id: shortId.generate(), User: { id: shortId.generate(), nickname: faker.name.findName(), }, content: faker.lorem.paragraph(), Images: [{ src: faker.image.image(), }], Comments: [{ User: { id: shortId.generate(), nickname: faker.name.findName(), }, content: faker.lorem.sentence(), }], })) export const LOAD_POST_REQUEST = 'LOAD_POST_REQUEST' export const LOAD_POST_SUCCESS = 'LOAD_POST_SUCCESS' export const LOAD_POST_FAILURE = 'LOAD_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 addPostAction = (data) => { console.log(data) return { type: ADD_POST_REQUEST, data } } export const addCommentAction = (data) => { console.log(data) return { type: ADD_COMMENT_REQUEST, data } } 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) => { return produce(state, (draft) => { switch (action.type) { case LOAD_POST_REQUEST: draft.getPostLoadding = true draft.getPostDone = false draft.getPostErr = null break; case LOAD_POST_SUCCESS: draft.getPostLoadding = false draft.getPostDone = true draft.mainPosts = action.data.concat(draft.mainPosts) draft.hasMorePost = draft.mainPosts.length < 50 break; case LOAD_POST_FAILURE: draft.getPostLoadding = false draft.getPostErr = action.err break; case ADD_POST_REQUEST: draft.addPostLoadding = true draft.addPostDone = false draft.addPostErr = null break; case ADD_POST_SUCCESS: draft.addPostLoadding = false draft.addPostDone = true draft.mainPosts.unshift(dummyPost(action.data)) break; case ADD_POST_FAILURE: draft.addPostLoadding = false draft.addPostErr = action.err break; case REMOVE_POST_REQUEST: draft.removePostLoadding = true draft.removePostDone = false draft.removePostErr = null break; case REMOVE_POST_SUCCESS: draft.removePostLoadding = false draft.removePostDone = true draft.mainPosts = state.mainPosts.filter((item) => item.id !== action.data) break; case REMOVE_POST_FAILURE: draft.removePostLoadding = false draft.removePostErr = action.err break; case ADD_COMMENT_REQUEST: draft.addCommentLoadding = true draft.addCommentDone = false draft.addCommentErr = null break; case ADD_COMMENT_SUCCESS: const post = draft.mainPosts.find((item) => item.id === action.data.postId) post.Comments.unshift((dummyComment(action.data.content))) draft.addCommentLoadding = false draft.addCommentDone = true break; case ADD_COMMENT_FAILURE: draft.addCommentLoadding = false draft.addCommentErr = action.error default: return state } }) } export default reducer import { delay, all, fork, takeLatest, put, throttle } from "redux-saga/effects"; import { ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE, ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_COMMENT_FAILURE, LOAD_POST_REQUEST, LOAD_POST_SUCCESS, LOAD_POST_FAILURE, getDemmuyPost } from '../reducers/post' import { ADD_POST_TO_ME, REMOVE_POST_TO_ME } from "../reducers/user"; import shortId from 'shortid'; function addPostAPI(data) { return axios.post('/api/addPost', data) } function* addPost(action) { try { // const result = yield call(addPostAPI, action.data) yield delay(1000) const id = shortId.generate(); yield put({ type: ADD_POST_SUCCESS, data: { id, content: action.data } }) yield put({ type: ADD_POST_TO_ME, data: id }) } catch (err) { yield put({ type: ADD_POST_FAILURE, data: err.response.data }); } } function removePostAPI(data) { return axios.post('/api/addPost', data) } function* removePost(action) { try { // const result = yield call(addPostAPI, action.data) yield delay(1000) yield put({ type: REMOVE_POST_SUCCESS, data: action.data }) yield put({ type: REMOVE_POST_TO_ME, data: action.data }) } catch (err) { yield put({ type: REMOVE_POST_FAILURE, data: err.response.data }); } } function addCommentAPI(data) { return axios.post('/api/addPost', data) } function* addComment(action) { try { // const result = yield call(addPostAPI, action.data) yield delay(1000) yield put({ type: ADD_COMMENT_SUCCESS, data: action.data }); } catch (err) { yield put({ type: ADD_COMMENT_FAILURE, data: err.response.data }); } } function loadPostAPI(data) { return axios.get('/api/posts', data) } function* loadPost(action) { try { // const result = yield call(addPostAPI, action.data) yield delay(1000) yield put({ type: LOAD_POST_SUCCESS, data: getDemmuyPost(10) }); } catch (err) { yield put({ type: LOAD_POST_FAILURE, data: err.response.data }); } } function* watchLoadPost() { yield throttle(5000, LOAD_POST_REQUEST, loadPost) } function* watchAddPost() { yield takeLatest(ADD_POST_REQUEST, addPost) } function* watchRemovePost() { yield takeLatest(REMOVE_POST_REQUEST, removePost) } function* watchCommentPost() { yield takeLatest(ADD_COMMENT_REQUEST, addComment) } export default function* postSaga() { yield all([ fork(watchAddPost), fork(watchCommentPost), fork(watchRemovePost), fork(watchLoadPost), ]); }