월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
POST http://localhost:3065/post 500 (Internal Server Error) 500에러 질문 입니다
redux toolkit을 이용해서 코딩을 하고 있고 credentials까지 강의 나와있는대로 다 작성을 한 상태입니다post action creator는 이렇게 작성하였고export const addPost = createAsyncThunk( "/post", async (data, { fulfillWithValue, rejectWithValue }) => { try { console.log(data); const response = await axios.post("/post", data); console.log(response.data); return fulfillWithValue(response.data); } catch (error) { throw rejectWithValue(error.response); } } );에러메시지는 POST http://localhost:3065/post 500 (Internal Server Error)redux dev tools에 rejected 요청으로 온 여러 정보가 있는데 그중에 뭐가 필요한 정보인지 몰라서 제로초님이 필요하신 정보를 얘기해주시면 추가적으로 추가 하겠습니다status(pin):500statusText(pin):"Internal Server Error"일단 이 두개가 상태 메시지입니다
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
Fast Refresh Warning
터미널에 다음과 같은 경고가 뜹니다.warn - Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works TypeError: Failed to fetch at webpack_require.hmrM (http://localhost:3000/_next/static/chunks/webpack.js?ts=1690436755699:1201:20)작성한 코드AppLayout.jsimport { Menu } from "antd"; import Link from "next/link"; import PropTypes from "prop-types"; import React from "react"; const items = [ { label: ( <Link href="/"> <a>홈</a> </Link> ), key: "home", }, { label: <Link href="/profile">프로필</Link>, key: "profile", }, { label: ( <Link href="/signup"> <a>회원가입</a> </Link> ), key: "signup", }, ]; function AppLayout({ children }) { return ( <div> <Menu mode="horizontal" items={items}></Menu> {children} </div> ); } AppLayout.propTypes = { children: PropTypes.node.isRequired, }; export default AppLayout; index.jsimport React from "react"; import AppLayout from "../components/AppLayout"; function Home() { return ( <AppLayout> <div>Hello, Next!</div> </AppLayout> ); } export default Home; Fast Refresh 관련 문서를 보고 새로 고침도 해보고, 캐시도 지워보고, 익명 함수를 기명 함수로 바꿔보았지만 소용이 없었습니다.npm run dev를 실행하고 localhost에 접속할 때마다 보입니다.웹페이지 우측 하단에 vercel mark 같은 것이 잠시 깜빡이는데 그 때에도 fast refresh 관련 warnning이 뜹니다.next.js 12 버전, antd 5버전을 사용 중이며 질문 시점에 강의는 Section1의 1강 수강 했습니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
서로 다른 팔로우 버튼을 빠르게 눌렀을때 둘다 팔로우 성공시키는 방법?
(클릭한 버튼만 loading이 되게 해놓은 상태에서)서로 다른 두개의 팔로우 버튼을 빠르게(1초안에) 클릭했을때요청이 두번가는데 두번째거만 성공하는 이유는takeLatest를 사용해서 그런것이 맞나요?둘다 성공시키기 위해 takeEvery를 사용했는데그래도 될까요?function* watchFollow() { yield takeEvery(FOLLOW_REQUEST, follow); } function* watchUnfollow() { yield takeEvery(UNFOLLOW_REQUEST, unfollow); } 그리고 혹시 takeLatest를 써도 서버에 여러개의 요청이 가니까 서버에서 여러 요청을 모두 성공하게끔 처리가 될까요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
util/produce.js 에서 오류가 납니다
npm run dev를 했는데아래와 같은 오류가 뜨면서 실행이 안됩니다어디서 문제가 있는 건가요? util/produce.js 코드 내용import { enableES5, produce } from 'immer'; export default (...args) => { enableES5(); return produce(...args); }; 터미널 에러> react-nodebird-front@1.0.0 dev> next -p 3060ready - started server on 0.0.0.0:3060, url: http://localhost:3060event - compiled client and server successfully in 668 ms (1830 modules)wait - compiling / (client and server)...event - compiled client and server successfully in 1185 ms (4446 modules)4. WrappedApp created new store with withRedux(NodeBird) { initialState: undefined, initialStateFromGSPorGSSR: undefined }{}wait - compiling /_error (client and server)...error - util/produce.js (4:11) @ __WEBPACK_DEFAULT_EXPORT__error - TypeError: (0 , immer__WEBPACK_IMPORTED_MODULE_0__.enableES5) is not a function at __WEBPACK_DEFAULT_EXPORT__ (webpack-internal:///./util/produce.js:11:53) at reducer (webpack-internal:///./reducers/user.js:115:106) at /Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/node_modules/redux/lib/redux.js:476:24 at Array.forEach (<anonymous>) at assertReducerShape (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/node_modules/redux/lib/redux.js:474:25) at combineReducers (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/node_modules/redux/lib/redux.js:539:5) at eval (webpack-internal:///./reducers/index.js:19:75) { page: '/'} 2 | 3 | export default (...args) => {> 4 | enableES5(); | ^ 5 | return produce(...args); 6 | };event - compiled client and server successfully in 315 ms (4447 modules)4. WrappedApp created new store with withRedux(NodeBird) { initialState: undefined, initialStateFromGSPorGSSR: undefined }{}TypeError: (0 , immer__WEBPACK_IMPORTED_MODULE_0__.enableES5) is not a function at __WEBPACK_DEFAULT_EXPORT__ (webpack-internal:///./util/produce.js:11:53) at reducer (webpack-internal:///./reducers/user.js:115:106) at /Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/node_modules/redux/lib/redux.js:476:24 at Array.forEach (<anonymous>) at assertReducerShape (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/node_modules/redux/lib/redux.js:474:25) at combineReducers (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/node_modules/redux/lib/redux.js:539:5) at eval (webpack-internal:///./reducers/index.js:19:75) at processTicksAndRejections (node:internal/process/task_queues:96:5)^C%
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
cosnt is not defined
npm run dev 시 cosnt is not defined 가 뜹니다 제 코드는import React from 'react'; import propTypes from 'prop-types'; import Link from 'next/link'; import { Menu, Input, Row, Col } from 'antd'; import 'antd/dist/antd.css' import UserProfile from '../components/UserProfile'; import LoginForm from '../components/LoginForm'; const AppLayout = ({ children }) => { cosnt [IsLoggedIn, setIsLoggedIn] = useState(false); return ( <div> <Menu mode="horizontal"> <Menu.Item> <Link href="/"><a>노드버드</a></Link> </Menu.Item> <Menu.Item> <Link href="/profile"><a>프로필</a></Link> </Menu.Item> <Menu.Item> <Input.Search enterButton style={{verticalAlign: 'middle' }}/> </Menu.Item> <Menu.Item> <Link href="/signup"><a>회원가입</a></Link> </Menu.Item> </Menu> <Row gutter={8}> <Col xs={24} md={6}> {isLoggedIn ? <Userprofile /> : <LoginForm />} </Col> <Col xs={24} md={12}> {children} </Col> <Col xs={24} md={6}> <a href="" target='_blank' rel='noreferrer noopener'>Made by 0880</a> </Col> </Row> </div> ); }; AppLayout.propTypes = { children: propTypes.node.isRequired, } export default AppLayout; 입니다 무엇이 문제 일까요
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
App is not defined
npm run dev를 했을 시 ReferenceError: App is not defined 이렇게 뜹니다 제 코드는 import React from 'react'; import PropTypes from 'prop-types'; import Head from 'next/head'; import 'antd/dist/antd.css' const NodeBird = ({Component}) => { return ( <> <Head> <meta charset='utf-8' /> <title>NodeBird</title> </Head> <Component /> </> ) }; App.PropTypes = { Component: PropTypes.elementType.isRequired, } export default App;입니다 어떻게 해야 하나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
null 속성을 읽을 수 없음.
npm run dev를 할 시 Cannot read properties of null (reading 'prefixCls')이렇게 뜹니다..제 코드는 이렇게 되어 있는데 무엇이 문제일까요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
시퀄라이즈 메소드 findOrCreate 옵션 에러
현재 에러Error: Missing where attribute in the options parameter passed to findOrCreate. Please note that the API has changed, and is now options only (an object with where, defaults keys, transaction etc.) at Function.findOrCreate (/Users/yang-areum/Desktop/study/React-NodeBirdSNS/prepare/back/node_modules/sequelize/lib/model.js:1388:13) at /Users/yang-areum/Desktop/study/React-NodeBirdSNS/prepare/back/routes/post.js:50:19 at Array.map (<anonymous>) at /Users/yang-areum/Desktop/study/React-NodeBirdSNS/prepare/back/routes/post.js:49:18 at processTicksAndRejections (node:internal/process/task_queues:96:5) Error: Missing where attribute in the options parameter passed to findOrCreate. Please note that the API has changed, and is now options only (an object with where, defaults keys, transaction etc.) at Function.findOrCreate (/Users/yang-areum/Desktop/study/React-NodeBirdSNS/prepare/back/node_modules/sequelize/lib/model.js:1388:13) at /Users/yang-areum/Desktop/study/React-NodeBirdSNS/prepare/back/routes/post.js:50:19 at Array.map (<anonymous>) at /Users/yang-areum/Desktop/study/React-NodeBirdSNS/prepare/back/routes/post.js:49:18 at processTicksAndRejections (node:internal/process/task_queues:96:5) POST /post 500 17.278 ms - 820 번역 해본 결과 :현재 코드 :현재 해시태그와 관련된 라우터코드만 올려보아요.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"); } // multer 미들웨어 : 게시물 이미지 업로드 설정 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); // 파일명 + _ + 시간초 + 확장자 }, }), limits: { fileSize: 20 * 1024 * 1024 }, }); // 게시글 업로드 router.post("/", isLoggedIn, upload.none(), async (req, res, next) => { // POST /post try { // 해시태그 추출 const hashtags = req.body.content.match(/#[^\s]+/g); const post = await Post.create({ content: req.body.content, UserId: req.user.id, // 게시글을 작성한 사용자 id }); // 해시태그 등록 if (hashtags) { const result = await Promise.all( hashtags.map((tag) => Hashtag.findOrCreate({ wehre: { name: tag.slice(1).toLowerCase() }, }) ) ); console.log("HASHTAG", result); await post.addHashtags(result.map((v) => v[0])); } // 받은 이미지 시퀄라이즈, DB저장 if (req.body.image) { if (Array.isArray(req.body.image)) { // 이미지를 여러개 올리면 image: [파일명.png, 파일명.png] const images = await Promise.all( req.body.image.map((image) => Image.create({ src: image })) ); 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 (err) { console.error(err); next(err); } }); 혹시나 하고 시퀄라이즈에서 모델에서 코드를 잘 못 쳐서 DB가 잘못 설계 된것인가? 도 생각이 들어 코드확인과 워크밴치에서 테이블 확인했는데 문제점은 없다고 생각을 했어요./* models.index.js */ const Sequelize = require("sequelize"); const env = process.env.NODE_ENV || "development"; const config = require("../config/config")[env]; const db = {}; const sequelize = new Sequelize( config.database, config.username, config.password, config ); // 시퀄라이즈에서 모델을 등록 (시퀄라이즈로 db에 require들을 넣어서 forEach문으로) db.Comment = require("./comment")(sequelize, Sequelize); db.Hashtag = require("./hashtag")(sequelize, Sequelize); db.Image = require("./image")(sequelize, Sequelize); db.User = require("./user")(sequelize, Sequelize); db.Post = require("./post")(sequelize, Sequelize); Object.keys(db).forEach((modelName) => { if (db[modelName].associate) { db[modelName].associate(db); } }); db.sequelize = sequelize; db.Sequelize = Sequelize; module.exports = db;/* medels/post.js */ module.exports = (sequelize, DataTypes) => { const Post = sequelize.define( "Post", { // id: {}, MySQL에서 id가 자동으로 생성된다. content: { type: DataTypes.TEXT, // 글자를 무제한으로 늘려주기위해 TEXT를 사용해보았다. STRING으로 한다. allowNull: false, }, }, { // Post Module에 대한 셋팅 charset: "utf8mb4", // 한글 + 이모티콘 collate: "utf8mb4_general_ci", // 한글 + 이모티콘 저장 } ); // belongsTo 단수, hasMany 복수 (뒤에 s) Post.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" }); db.Post.belongsTo(db.Post, { as: "Retweet" }); }; return Post; };/* models/hashtag.js */ module.exports = (sequelize, DataTypes) => { const Hashtag = sequelize.define( "Hashtag", { // id: {}, MySQL에서 id가 자동으로 생성된다. name: { type: DataTypes.STRING(20), allowNull: false, }, }, { // Hashtag Module에 대한 셋팅 charset: "utf8mb4", // 한글 + 이모티콘 collate: "utf8mb4_general_ci", // 한글 + 이모티콘 저장 } ); Hashtag.associate = (db) => { db.Hashtag.belongsToMany(db.Post, { through: "PostHashtag" }); // 하나의 해시태그에 여러개의 게시글 (M:N의 관계) }; return Hashtag; }; 그리고 당연히 워크밴치에서는 게시글은 잘 들어갔지만, 해시태그 테이블은 아무것도 저장되지 않았죠.게시글해시태그 질문지금 번역도 해보고 구글링도 해본 결과, 원인은 findOrCreate메소드를 사용하면서 전달해주는 where속성이 누락되었다고 하더라구요..구글링을 해보니 첫번째 : where, 두번째 : 기본 키 (값), 세번째 : transaction 값을 넣어야된다고 나와있더라구요.궁금한게 강좌에서 설명하는 속성값과 지금 제가 검색해서 알게 된 넣어야되는 속성값이 다른이유가 있을까요 ?그리고 결국 결론은, 지금 현재 이 에러를 해결하기 위해서 기본값을 설정해주어야하는 부분인가요 ?
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
파비콘 안뜨는 이유
안녕하세요 선생님 고생이 많으십니다._app.js 헤드에 코드도 추가했고 front 폴더안에 public 폴더만들어서 파비콘 이미지도 넣어놨는데 왜 파비콘이 지구모양에서 안바뀔까요.. 체크해 볼 경우의 수가 머가 있을지 궁금합니다.. <link rel="shortcut icon" href="/favicon.ico" />
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
'next'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다. 문제
npm run dev를 하니 'next'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는배치 파일이 아닙니다.라고 뜨는데 어떻게 고치나요
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
서버 들어갈 시 에러
- error Error: The default export is not a React Component in page: "/" at renderToHTMLImpl (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\render.js:279:19) at PagesRouteModule.render (webpack-internal:///./node_modules/next/dist/server/future/route-modules/pages/module.js:31:45) at doRender (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:1055:40) at cacheEntry.responseCache.get.incrementalCache.incrementalCache (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:1215:34) at C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\response-cache\index.js:99:42 at ResponseCache.get (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\response-cache\index.js:149:11) at DevServer.renderToResponseWithComponentsImpl (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:1134:53) at C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:727:121 at NextTracerImpl.trace (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\lib\trace\tracer.js:90:20) at DevServer.renderToResponseWithComponents (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:727:41) at DevServer.renderPageComponent (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:1366:35) at processTicksAndRejections (node:internal/process/task_queues:96:5) at async DevServer.renderToResponseImpl (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:1398:32) at async DevServer.pipeImpl (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:645:25) at async Object.fn (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\next-server.js:1153:21) at async Router.execute (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\router.js:315:32) at async DevServer.runImpl (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:619:29) at async DevServer.run (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\dev\next-dev-server.js:908:20) at async DevServer.handleRequestImpl (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:546:20) { digest: undefined }저번 폴더구조 문제를 해결하니 이러한 문제가 생겼습니다 어떻게 하나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
pages 또는 앱 디렉토리를 찾을 수 없음 에러
- ready started server on 0.0.0.0:3000, url: http://localhost:3000 Error: > Couldn't find any `pages` or `app` directory. Please create one under the project root at findPagesDir (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\lib\find-pages-dir.js:54:15) at DevServer.getRoutes (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\dev\next-dev-server.js:191:71) at new Server (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\base-server.js:198:47) at new NextNodeServer (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\next-server.js:175:9) at new DevServer (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\dev\next-dev-server.js:149:9) at NextServer.createServer (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\next.js:179:24) at C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\next.js:200:42 at async NextServer.prepare (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\next.js:161:24) at async Server.<anonymous> (C:\Users\USER\Desktop\1080\react\front\node_modules\next\dist\server\lib\render-server.js:128:17) { type: 'Error'npm run dev를 했을 때 이런식으로 뜨는데 어떻게 해야 하나요??
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
useSWR 로직을 hooks로 사용하는 경우
로그인 유지를 swr 방식으로 교체하려고 합니다.https://swr.vercel.app/ko/docs/getting-started공식문서에는 useSWR을 hook으로 활용하는 가이드를 제시하고 있습니다.(재사용 가능하게 만들기 항목)가이드와 같은 방식으로 외부 파일에 hooks를 만들고, 적용했습니다.useMyInfo.jsimport axios from 'axios'; export default function useMyInfo(useSWR) { const fetcher = url => axios.get(url, { withCredentials: true }).then(result => result.data); const { data: me, error: myInfoError, isLoading: myInfoLoading, } = useSWR(`${process.env.NEXT_PUBLIC_BACK_END_DOMAIN}/user/me`, fetcher); // mutate('ME', { me, myInfoError, myInfoLoading }); return { me, myInfoError, myInfoLoading, }; } index.jsconst Home = () => { const { freePosts, loadFreePostsStatus, postTotal, addPostStatus } = useSelector(state => state.post); useEffect(() => { useMyInfo(); // hooks 호출 }, []); ...// 생략매개변수로 전달하는 방식으로도 바꿔봤습니다.import useSWR from 'swr' const Home = () => { const { freePosts, loadFreePostsStatus, postTotal, addPostStatus } = useSelector(state => state.post); useEffect(() => { useMyInfo(useSWR); // hooks 호출, hooks에서는 매개변수 받아서 적용하는 방식으로 변경했음 }, []); ...// 생략여전히 같은 에러가 뜹니다.공식문서 가이드와 달리 hook으로 만들어 재사용하는 것이 잘 안되는데 어떻게 해결할수 있나요? 간단한 get요청 하나 가져오는게 편할줄 알았는데 redux 보다 어려운거 같네요..
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
게시글 작성시 post.id를 읽지 못 하는 문제가 있습니다.
안녕하세요, 제로초님. 문제에 대한 고민을 공유하기 앞서 현재 저는 [해시태그 등록하기]까지 수강한 상태입니다.공유하려는 문제는 제목과 같이 게시글 작성시 post.id를 읽지 못 하여 컴포넌트가 렌더링 되지 않습니다. 그러나 새로고침을 하면 작성한 게시글이 정상적으로 렌더링됩니다. 네트워크와 redux 데브툴즈에는 액션들이 모두 정상적으로 작동하고 있음을 확인하였고, 콘솔을 확인해보니 아래와 같은 오류가 뜹니다.위 오류는 비동기로 데이터를 받아오기 전에 먼저 render가 되서 발생하는 에러임을 검색을 통해 확인하였습니다. 그래서 react suspense를 이용하여 해결해보려 했으나 해결하지 못 했습니다. 혹시 제가 해당 오류의 원인을 제대로 파악하지 못 하고 있는 걸까요? 알려주시면 감사하겠습니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
버전 문의
버전 문의있습니ㄷㅏ.지금 최신버전으로 만ㄷㅡㄹ고싶으면 제로초님의 github에서 ch7을 보면 ㄷㅚㄹ까요?
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
서버사이드렌더링 쿠키저장시 리덕스에 있는 state에 action.data가 새로고침시에 사라짐
로그인시에 서버사이드렌더링으로 쿠키가 저장되고 공유하는건 확인이 됐습니다. 헤드에 저장이 되어 쿠키 저장이 된건 확인이 됐는데 로그인시 리덕스에서 me값으로 action.data가 전달되는데 왜 새로고침시에 state 인 me값이 왜 null 값으로 바뀌는걸까요.. 이것도 서버사이드 렌더링으로 유지가 가능할까요? 여러페이지를 만들시에 페이지이동시에도 처음 로그인시에 me 로 넘겨주는 action.data 값도 유지하고 싶습니다
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
게시글 삭제 시, 얕은 복사를 사용하지 않는 이유가 궁금합니다.
[ 게시글 삭제 saga 작성하기 ] 수강 중 질문 드립니다.이전 댓글을 추가할때는 reudce에서 case ADD_COMMENT_SUCCESS: { const postIndex = state.mainPosts.findIndex(v => v.id === action.data.postId); const post = { ...state.mainPosts[postIndex] }; // 얕은 복사 const Comments = [action.data, ...post.Comments]; const mainPosts = [...state.mainPosts]; mainPosts[postIndex] = { ...post, Comments, }; return { ...state, mainPosts, addCommentLoading: false, addCommentDone: true, addCommentError: false, }; }이런식으로 얕은 복사를 하셨는데요. case REMOVE_POST_SUCCESS: { const mainPosts = state.mainPosts.filter(v => v.id !== action.data.id); return { ...state, mainPosts, removePostLoading: false, removePostDone: true, removePostError: false, }; }게시글을 삭제할때는 왜 'state.mainPosts'를 얕은 복사해서 조작하지 않는 건지 궁금합니다.혹시 filter가 기존 state.mainPosts 을 수정하지 않으며, 새로운 배열을 return하기에 얕은 복사가 필요없는 건가요? 답변 기다리겠습니다.감사합니다 :)
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
CSR에서 로그인 유지시 깜빡임을 없애는 방법이 있나요?
강좌에서는 SSR 적용전 CSR 만으로 로그인 유지를 할 때 초기상태가 로그인 유지가 안되있는 상태기 때문에 로그인정보 부분에 깜빡임이 발생하는데요. 혹시 CSR에서도 깜빡임 없이 로그인 유지를 할 수 있는 방법이 있나요? 혹시 리액트 라이프 사이클쪽을 건드려야 하는건가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
redux toolkit으로 action.error에 원하는 메시지 넣는 방
if (exUser) { return res.status(403).send("이미 사용중인 아이디입니다." ); }이렇게 send를 보내고redux toolkit에서는 이 메시지를 error.message에 넣는 방법을 잘 모르겠는데 어떻게 하면 되나요?위에 사진은 signup/rejected의 Action안에 있는 정보입니다
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
post에 게시글 추가시 오류
로그인 후 post를 추가하면 아래와 같이 오류가 납니다 ㅠㅠ!화면에러내용Unhandled Runtime Error TypeError: Cannot read properties of undefined (reading '0') Source components\PostCard.js (47:27) @ PostCard 45 | <Card 46 | // post안에 Images가 있는 경우 > 47 | cover={post.Images[0] && <PostImages images={post.Images} />} | ^ 48 | actions={[ 49 | <RetweetOutlined key="retweet" />, 50 | liked ? (콘솔 추가 에러내용react-dom.development.js:19527 The above error occurred in the <PostCard> component: in PostCard (at pages/index.js:24) in div (created by Context.Consumer) in Col (at AppLayout.js:57) in div (created by Context.Consumer) in Row (at AppLayout.js:53) in div (at AppLayout.js:35) in AppLayout (at pages/index.js:21) in Home (at _app.js:13) in NodeBird (created by withRedux(NodeBird)) in Provider (created by withRedux(NodeBird)) in withRedux(NodeBird) in ErrorBoundary (created by ReactDevOverlay) in ReactDevOverlay (created by Container) in Container (created by AppContainer) in AppContainer in Root page/index.jsimport { all, fork } from "redux-saga/effects"; import postSaga from "./post"; import userSaga from "./user"; export default function* rootSaga() { yield all([fork(postSaga), fork(userSaga)]); } reducer/post.jsimport shortId from "shortid"; import produce from "immer"; import immer from "immer"; export const initailState = { mainPosts: [ { id: 1, User: { id: 1, nickname: "행복", }, content: "첫 번째 게시글 #해시태그 #해시태그2", Images: [ { src: "https://bookthumb-phinf.pstatic.net/cover/137/995/13799585.jpg?udate=20180726", }, { src: "https://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: [], addPostLoading: false, addPostDone: false, addPostError: null, removePostLoading: false, removePostDone: false, removePostError: null, addCommentLoading: false, addCommentDone: false, addCommentError: null, }; 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 addPost = (data) => { return { type: ADD_POST_REQUEST, data, }; }; export const addComment = (data) => { return { type: ADD_COMMENT_REQUEST, data, }; }; const dummyPost = (data) => ({ id: data.id, content: data.content, User: { id: 1, nickname: "행복", }, }); const dummyComment = (data) => ({ id: shortId.generate(), content: data, User: { id: 1, nickname: "행복", }, Images: [], Comments: [], }); const reducer = (state = initailState, action) => { return produce(state, (draft) => { switch (action.type) { case ADD_POST_REQUEST: draft.addPostLoading = true; draft.addPostDone = false; draft.addPostError = null; break; case ADD_POST_SUCCESS: draft.mainPosts.unshift(dummyPost(action.data)); draft.addPostLoading = false; draft.addPostDone = true; break; case ADD_POST_FAILURE: draft.addPostLoading = false; draft.addPostError = action.error; break; case REMOVE_POST_REQUEST: draft.removePostLoading = true; draft.removePostDone = false; draft.removePostError = null; draft.mainPosts = state.main; break; case REMOVE_POST_SUCCESS: // draft.mainPosts = state.mainPosts.filter((v) => v.id !== action.data); draft.mainPosts = draft.mainPosts.filter((v) => v.id !== action.data); draft.removePostLoading = false; draft.removePostDone = true; break; case REMOVE_POST_FAILURE: draft.removePostLoading = false; draft.removePostError = 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(dummyComment(action.data.content)); draft.addCommentLoading = false; draft.addCommentDone = true; break; // const postIndex = state.mainPosts.findIndex( // (v) => v.id === action.data.postId // ); // const post = { ...state.mainPosts[postIndex] }; // post.Comments = [dummyComment(action.data.content), ...post.Comments]; // Comments 얕은 복사 후 dummyComment 추가 // const mainPosts = [...state.mainPosts]; // mainPosts 새로운객체 생성 // mainPosts[postIndex] = post; // return { // ...state, // mainPosts, // addCommentLoading: false, // addCommentDone: true, // }; } case ADD_COMMENT_FAILURE: draft.addCommentLoading = false; draft.addCommentError = action.error; break; default: break; } }); }; export default reducer; //기본 구조 // const initailState = {}; // const reducer = (state = initailState, action) => { // switch (action.type) { // default: // return state; // } // }; // export default reducer; // 데이터를 구성하고 그에따라 액션구성후 reducer작성 그 후 화면 구성 sagas/post.jsimport { all, fork, delay, put, takeLatest } from "redux-saga/effects"; // import axios from "axios"; import { 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 { REMOVE_POST_OF_ME } from "../reducers/user"; import { ADD_POST_TO_ME } from "../reducers/user"; import shortId from "shortid"; function addPostAPI(data) { return axios.post("/api/post", 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, error: err.response.data, }); } } function removePostAPI(data) { return axios.delete("/api/post", data); // 서버에 요청 } function* removePost(action) { try { yield delay(1000); yield put({ type: REMOVE_POST_SUCCESS, data: action.data, }); yield put({ type: REMOVE_POST_OF_ME, data: action.data, }); } catch (err) { yield put({ type: REMOVE_POST_FAILURE, error: err.response.data, }); } } function addcommentAPI(data) { return axios.post(`/api/post/${data.postId}/comment`, data); // 서버에 요청 } function* addComment(action) { try { // const result = yield call(addCommentAPI, action.data); yield delay(1000); yield put({ type: ADD_COMMENT_SUCCESS, data: action.data, }); } catch (err) { yield put({ type: ADD_COMMENT_FAILURE, error: err.response.data, }); } } function* watchAddPost() { yield takeLatest(ADD_POST_REQUEST, addPost); } function* watchRemovePost() { yield takeLatest(REMOVE_POST_REQUEST, removePost); } function* watchAddComment() { yield takeLatest(ADD_COMMENT_REQUEST, addComment); } export default function* postSaga() { yield all([fork(watchAddPost), fork(watchRemovePost), fork(watchAddComment)]); } components/PosdCard.jsimport React, { useState, useCallback } from "react"; import { Card, Popover, Button, Avatar, Comment, List } from "antd"; import PropTypes from "prop-types"; import CommentForm from "./CommentForm"; import { RetweetOutlined, HeartOutlined, MessageOutlined, EllipsisOutlined, HeartTwoTone, } from "@ant-design/icons"; import { useDispatch, useSelector } from "react-redux"; import PostImages from "./PostImages"; import PostCardContent from "./PostCardContent"; import { REMOVE_POST_REQUEST } from "../reducers/post"; const PostCard = ({ post }) => { const dispatch = useDispatch(); const { removePostLoading } = useSelector((state) => state.post); const [liked, setLiked] = useState(false); const [commentFormOpened, setCommentFormOpened] = useState(false); const onToggleLike = useCallback(() => { setLiked((prev) => !prev); }, []); const onToggleComment = useCallback(() => { setCommentFormOpened((prev) => !prev); }, []); const id = useSelector((state) => state.user.me?.id); // = const id = useSelector((state) => state.user.me && state.user.me.id); //const { me } = useSelector((state) => state.user); //const id = me?.id; // 로그인을 한 상태이고 아이디가 있으면 으로 해석해서 설계해보자 // const id = me && me.id; const onRemovePost = useCallback(() => { dispatch({ type: REMOVE_POST_REQUEST, data: post.id, }); }, []); return ( <div style={{ marginBottom: 20 }}> <Card // post안에 Images가 있는 경우 cover={post.Images[0] && <PostImages images={post.Images} />} actions={[ <RetweetOutlined key="retweet" />, liked ? ( <HeartTwoTone twoToneColor="#eb2f96" key="heart" onClick={onToggleLike} /> ) : ( <HeartOutlined key="heart" onClick={onToggleLike} /> ), <MessageOutlined key="comment" onClick={onToggleComment} />, <Popover key="more" content={ <Button.Group> {/* 내아이디랑 게시글작성아디가 나랑 같으면 */} {id && post.User.id === id ? ( <> <Button>수정</Button> <Button type="danger" onClick={onRemovePost} loading={removePostLoading} > 삭제 </Button> </> ) : ( <Button>신고</Button> )} </Button.Group> } > <EllipsisOutlined /> </Popover>, ]} > <Card.Meta avatar={<Avatar>{post.User.nickname[0]}</Avatar>} title={post.User.nickname} description={<PostCardContent postData={post.content} />} /> </Card> {commentFormOpened && ( <div> {/* post를 넘겨주는 이유는? 어떤 게시글에 댓글을 달건지 게시글의 id가 필요하기 때문에 */} <CommentForm post={post} /> <List header={`${post.Comments.length}개의 댓글`} itemLayout="horizontal" dataSource={post.Comments} //post.Comments가 각각 item으로 들어감 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.object, Comments: PropTypes.arrayOf(PropTypes.object), Images: PropTypes.arrayOf(PropTypes.object), }).isRequired, }; export default PostCard; componets/PostForm.jsimport React, { useCallback, useRef, useEffect } from "react"; import { Form, Input, Button } from "antd"; import { useDispatch, useSelector } from "react-redux"; import { addPost } from "../reducers/post"; import useInput from "../hooks/useInput"; const PostForm = () => { const { imagePaths, addPostDone } = useSelector((state) => state.post); const dispath = useDispatch(); const [text, onChangeText, setText] = useInput(""); useEffect(() => { if (addPostDone) { setText(""); } }, [addPostDone]); const onSubmit = useCallback(() => { dispath(addPost(text)); }, [text]); const imageInput = useRef(); const onClickImageUpload = useCallback(() => { imageInput.current.click(); }, [imageInput.current]); return ( <Form onFinish={onSubmit} style={{ margin: "10px 0 20px" }} encType="multipart/form-data" > <Input.TextArea value={text} onChange={onChangeText} maxLength={140} placeholder="어떤 신기한 일이?" /> <div> <input type="file" multiple hidden ref={imageInput} /> <Button onClick={onClickImageUpload}>이미지 업로드</Button> <Button type="primary" style={{ float: "right" }} htmlType="submit"> 등록 </Button> </div> <div> {imagePaths.map((v) => ( <div key={v} style={{ display: "inline-block" }}> <img src={v} style={{ width: "200px" }} alt={v} /> <div> <Button>제거</Button> </div> </div> ))} </div> </Form> ); }; export default PostForm;