월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
css 서버사이드 랜더링이 적용되지 않아서 문의 드립니다.
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=49018&category=questionDetail&tab=community&q=1075492안녕하세요 제로초님!위의 질문 답변을 보고 css 서버사이드 랜더링을 하기위해서 이것저것 해보았는데 적용이 안되서 문의 드립니다..babelrc 작성next.config.js에서도 옵션으로 가능하다고해서 수정_document.js 수정빌드 하고 npm start하고 테스트위 링크의 답변 참고해서 @ant-design/cssinjs 적용해봄 그럼에도 적용안되더라구요 ㅠpages/_docuemnts.js (이 전에는 제로초님깃헙의 것을 썼었습니다. 안돼서 @ant-design/cssinjs 적용한 버전입니다.import React from "react"; import Document, { Html, Head, Main, NextScript } from "next/document"; import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs"; import { ServerStyleSheet } from "styled-components"; export default class MyDocument extends Document { static async getInitialProps(ctx) { const cache = createCache(); const sheet = new ServerStyleSheet(); const originalRenderPage = ctx.renderPage; try { ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => ( <StyleProvider cache={cache}> <App {...props} /> </StyleProvider> ), }); const initialProps = await Document.getInitialProps(ctx); const style = extractStyle(cache, true); return { ...initialProps, styles: ( <> {initialProps.styles} <style dangerouslySetInnerHTML={{ __html: style }} /> </> ), }; } catch (error) { console.error(error); } finally { sheet.seal(); } } render() { return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html> ); } } next.config.jsconst withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: process.env.ANALYZE === "true", }); module.exports = withBundleAnalyzer({ images: { domains: ["react-nodebird.s3.ap-northeast-2.amazonaws.com", "react-nodebird-s3.s3.amazonaws.com"], }, compress: true, compiler: { styledComponents: { ssr: true, displayName: true, }, }, webpack(config, { webpack }) { const prod = process.env.NODE_ENV === "production"; return { ...config, mode: prod ? "production" : "development", devtool: prod ? "hidden-source-map" : "inline-source-map", plugins: [...config.plugins], }; }, }); .eslintrc{ "parser": "@babel/eslint-parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module", "ecmaFeatures": { "jsx": true }, "requireConfigFile": false, "babelOptions": { "presets": ["next/babel"] } }, "env": { "browser": true, "node": true, "es6": true }, "extends": [ "airbnb", "next/babel" ], "plugins": ["import", "react-hooks", "jsx-a11y"], "rules": { "react/react-in-jsx-scope": "off", "jsx-a11y/label-has-associated-control": "off", "jsx-a11y/anchor-is-valid": "off", "no-console": "off", "no-underscore-dangle": "off", "react/forbid-prop-types": "off", "react/jsx-filename-extension": "off", "react/jsx-one-expression-per-line": "off", "react/jsx-props-no-spreading": "off", "object-curly-newline": "off", "linebreak-style": "off", "no-param-reassign": "off", "max-len": "off" } } /.babelrc{ "presets": ["next/babel"], "plugins": [ [ "styled-components", { "ssr": true, "displayName": true } ] ] } https://github.com/dydcodydco/react-nodebird혹시나해서 깃헙 주소도 남깁니다.마지막으로 하나 더 궁금한게 있습니다.이 강의를 내껄로 만들고, 다음강의 슬랙까지 강의보고 하면중고신입으로 개발자 이직할 수 있을지도 궁금합니다.좋은 강의 정말 감사합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
팔로워 3명씩 불러오고 데이터 합쳐주는걸로 바꾸고 서버요청을 무한으로하고있습니다.
안녕하세요 제로초님.강의중에 말씀해주셨건걸 참고해서팔로워, 팔로잉을 3명 호출하고, 이후에 다음3명씩 호출하고 불러온 데이터를 합쳐서 리스트를 만드는식으로 바꿔봤습니다.그런데 이슈가 한번 불러오기, 더보기 다 작동하는데 서버 요청을 무한으로 하고 있습니다.어떤 부분을 수정해야할지 봐주실 수 있을까요?limit은 3으로 고정, page를 조절해서 다음 3명씩/pages/profile.jsimport Head from "next/head"; import { useDispatch, useSelector } from "react-redux"; import { useCallback, useEffect, useState } from "react"; import { useRouter } from "next/router"; import axios from "axios"; import useSWR from "swr"; import { loadFollowersRequestAction, loadFollowingsRequestAction, loadMyInfo } from "../reducers/user"; import AppLayout from "../components/AppLayout"; import NicknameEditForm from "../components/NicknameEditForm"; import FollowList from "../components/FollowList"; import wrapper from "../store/configurStore"; const fetcher = (url) => axios.get(url, { widthCredentials: true }).then((result) => { console.log("fetcher----------------------"); return result.data; }); const Profile = () => { const router = useRouter(); const dispatch = useDispatch(); const { me } = useSelector((state) => state.user); const [followersLimit, setFollowersLimit] = useState(1); const [followingsimit, setFollowingsLimit] = useState(1); const [followers, setFollowers] = useState([]); const [followings, setFollowings] = useState([]); const { data: followersData, error: followerError, isLoading: followerLoading, } = useSWR(`http://localhost:3065/user/followers?page=${followersLimit}`, fetcher, { onSuccess: (data) => { setFollowers((prev) => [...prev, ...data]); }, }); const { data: followingsData, error: followingError, isLoading: followingLoading, } = useSWR(`http://localhost:3065/user/followings?page=${followingsimit}`, fetcher, { onSuccess: (data) => { setFollowings((prev) => [...prev, ...data]); }, }); useEffect(() => { if (!(me && me.id)) { router.push("/"); } }, [me && me.id]); const loadMoreFollowings = useCallback(() => { setFollowingsLimit((prev) => prev + 1); }, []); const loadMoreFolloweers = useCallback(() => { setFollowersLimit((prev) => prev + 1); }, []); if (!me) { return <div>내정보 로딩중...</div>; } if (followerError || followingError) { console.error(followerError || followingError); return <div>팔로잉/팔로워 로딩 중 에러 발생...</div>; } return ( <> <Head> <title>내 프로필 | NodeBird</title> </Head> <AppLayout> <NicknameEditForm /> <FollowList header='팔로워' data={followers} onClickMore={loadMoreFolloweers} loading={followerLoading} /> <FollowList header='팔로잉' data={followings} onClickMore={loadMoreFollowings} loading={followingLoading} /> </AppLayout> </> ); }; export const getServerSideProps = wrapper.getServerSideProps((store) => async ({ req }) => { console.log(req.headers); const cookie = req ? req.headers.cookie : ""; axios.defaults.headers.Cookie = ""; if (req && cookie) { axios.defaults.headers.Cookie = cookie; } await store.dispatch(loadMyInfo()); }); export default Profile; /routes/userl.js// GET /user/followers 팔로워즈 불러오기 router.get("/followers", isLoggedIn, async (req, res, next) => { try { // 나를 먼저 찾고 const user = await User.findOne({ where: { id: req.user.id }, }); // 내 팔로워즈 get 하기 const limit = parseInt(req.query.limit, 10) || 3; // 기본값 3 const page = parseInt(req.query.page, 10) || 1; // 기본값 1 const offset = (page - 1) * limit; const followers = await user.getFollowers({ limit, offset }); res.status(200).json(followers); } catch (error) { console.error(error); next(error); } }); // GET /user/followings 팔로잉즈 불러오기 // 미들웨어... (req, res, next) 이 콜백함수도 미들웨어 router.get("/followings", isLoggedIn, async (req, res, next) => { try { const user = await User.findOne({ where: { id: req.user.id }, }); const limit = parseInt(req.query.limit, 10) || 3; // 기본값 3 const page = parseInt(req.query.page, 10) || 1; // 기본값 1 const offset = (page - 1) * limit; const followings = await user.getFollowings({ limit, offset }); res.status(200).json(followings); } catch (error) { console.error(error); next(error); } });
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
해시태그 검색에서 throttle에 관해 질문있습니다.
해시태그 검색에서 throttle에 관해 질문있습니다.해시태그 검색을 하게 되면 throttle때문에 5초 이후에 검색이 되고 있습니다.스크롤할 때에는 유용하지만 하나를 클릭하고, 5초 이내에 클릭하게되면 url은 바뀌지만 게시글은 바뀌지 않고 ux에 좋지않아보입니다. const loadHashtagPostsThrottle = async (payload) => { const queryStr = setQuerystring(payload); const url = `/hashtag/${encodeURIComponent(payload.tag)}${queryStr ? "?" + queryStr : ""}`; const response = await axios.get(url); return response; }; export const loadHashtagPosts = createAsyncThunk("post/loadHashtagPosts", _.throttle(loadHashtagPostsThrottle, 5000)); 특저 유저의 게시글을 검색할때도 비슷한 현상이 나올거같은데 이럴땐 어떻게 하면 ux를 좋게 할 수 있을지 궁금합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
full reload 될때는 서버 데이터를 호출하는데 그 이후로 새로고침할대는 호출하지 않아서 답답합니다
안녕하세요 제로초님full reload 될때는 서버 데이터를 호출하는데 그 이후로 새로고침할대는 호출하지 않아서 답답합니다.구글링하고 제로초님 로직을 따라 만들어보다가posts게시물이 내려오도록 수정을 했는데,이상하게 처음엔 서버에 요청하고 데이터를 내려받아서 게시물이 보이는데, 그이후 새로고침하면 서버 호출까지도 안하는것 같습니다.이젠 좀 나오나 싶었는데, full reload될때만 되서 여유되실 때 로직한번 봐주시면 감사하겠습니다 ㅠ / pages/index.jsimport { useDispatch, useSelector } from "react-redux"; import AppLayout from "../components/AppLayout"; import PostForm from "../components/PostForm"; import PostCard from "../components/PostCard"; import { useEffect } from "react"; import { loadPostsRequestAction } from "../reducers/post"; import { loadMyInfoRequestAction } from "../reducers/user"; import wrapper from "../store/configurStore"; import axios from "axios"; import { END } from "redux-saga"; // 프론트, 브라우저 같이 실행 const Home = () => { const { me } = useSelector((state) => state.user); const { mainPosts, hasMorePosts, loadPostsLoading, retweetError } = useSelector((state) => state.post); const dispatch = useDispatch(); useEffect(() => { if (retweetError) { alert(retweetError); } }, [retweetError]); useEffect(() => { const onScroll = () => { if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) { if (hasMorePosts && !loadPostsLoading) { const lastId = mainPosts[mainPosts.length - 1]?.id; dispatch(loadPostsRequestAction({ lastId, limit: 10 })); } } }; window.addEventListener("scroll", onScroll); return () => { window.removeEventListener("scroll", onScroll); }; }, [hasMorePosts, loadPostsLoading, mainPosts]); return ( <AppLayout> {me && <PostForm />} {/* 순서가 바뀌거나 삭제될 수 있는 리스트들에 key값으로 index를 쓰면 안됀다. */} {/* 반복문이 있고 바뀌지 않는 리스트일 경우에만 사용해도 된다. */} {mainPosts.map((post) => ( <PostCard key={post.id} post={post} /> ))} </AppLayout> ); }; export const getServerSideProps = wrapper.getServerSideProps((store) => async ({ req }) => { store.dispatch(loadPostsRequestAction()); store.dispatch(loadMyInfoRequestAction()); store.dispatch(END); await store.sagaTask.toPromise(); // 사가 작업 완료 대기 console.log("state", store.getState()); }); export function reportWebVitals(metric) { console.log(metric); } export default Home; /pages/_app.jsimport PropTypes from "prop-types"; import Head from "next/head"; import { Provider } from "react-redux"; import wrapper from "../store/configurStore"; const NodeBird = ({ Component, ...rest }) => { const { store, props } = wrapper.useWrappedStore(rest); const { pageProps } = props; return ( <Provider store={store}> <Head> <meta charSet='utf-8' /> <meta name='viewport' content='width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' /> <title>NodeBird</title> </Head> <Component {...pageProps} /> </Provider> ); }; NodeBird.propTypes = { Component: PropTypes.elementType.isRequired, pageProps: PropTypes.any.isRequired, }; export function reportWebVitals(metric) { // console.log("-----------------------------------"); // console.log(metric); } export default NodeBird; store/configureStore.jsimport { createWrapper } from "next-redux-wrapper"; import { configureStore } from "@reduxjs/toolkit"; import reducer from "../reducers"; import user from "../reducers/user"; import post from "../reducers/post"; import createSagaMiddleware from "redux-saga"; import rootSaga from "../sagas"; // redux-thunk를 참조해서 만든 미들웨어 const loggerMiddleware = ({ dispatch, getState }) => (next) => (action) => { return next(action); }; const sagaMiddleware = createSagaMiddleware(); function getServerState() { return typeof document !== "undefined" ? JSON.parse(document.querySelector("#__NEXT_DATA__").textContent)?.props.pageProps.initialState : undefined; } const serverState = getServerState(); const makeStore = () => { // configureStore: store 를 생성 const store = configureStore({ reducer: { user, post, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat([sagaMiddleware, loggerMiddleware]), preloadedState: serverState, // SSR }); store.sagaTask = sagaMiddleware.run(rootSaga); return store; }; const wrapper = createWrapper(makeStore, { debug: process.env.NODE_ENV === "development", }); export default wrapper; /reducers.js/post.js import { HYDRATE } from "next-redux-wrapper"; import { createSlice } from "@reduxjs/toolkit"; import shortId from "shortid"; import produce from "immer"; export const initialState = { mainPosts: [], imagePaths: [], hasMorePosts: true, loadPostsLoading: false, // 게시글들 불러오는 중 loadPostsDone: false, loadPostsError: null, addPostLoading: false, // 게시글 추가 시도중 addPostDone: false, addPostError: null, removePostLoading: false, // 게시글 제거 시도중 removePostDone: false, removePostError: null, addCommentLoading: false, // 댓글 추가 시도중 addCommentDone: false, addCommentError: null, likePostLoading: false, // 좋아요 시도중 likePostDone: false, likePostError: null, unLikePostLoading: false, // 좋아요 취소중 unLikePostDone: false, unLikePostError: null, uploadImagesLoading: false, // 이미지 업로드 시도중 uploadImagesDone: false, uploadImagesError: null, retweetLoading: false, // 리트윗 시도중 retweetDone: false, retweetError: null, }; const postSlice = createSlice({ name: "post", initialState, reducers: {loadPostsRequestAction: (state, action) => { console.log("-------------------요청-------------------"); state.loadPostsLoading = true; state.loadPostsDone = false; state.loadPostsError = null; }, loadPostsSuccessAction: (state, action) => { console.log("-------------------성공-------------------"); state.loadPostsLoading = false; state.loadPostsDone = true; state.mainPosts = [... state.hasMorePosts = action.payload.length === 10; }, loadPostsFailureAction: (state, action) => { console.log("-------------------실패-------------------"); state.loadPostsLoading = false; state.loadPostsError = action.payload; }, }, extraReducers: (builder) => builder .addCase(HYDRATE, (state, action) => { console.log("HYDRATE", action); return { ...state, ...action.payload.post, }; }) .addDefaultCase((state) => state), }); export const { loadPostsRequestAction, loadPostsSuccessAction, loadPostsFailureAction, } = postSlice.actions; export default postSlice.reducer;
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
getServerSideProps에서 데이터가 제대로 내려오지 않는듯합니다.
안녕하세요 제로초님https://github.com/ZeroCho/react-nodebird/blob/master/toolkit/front/pages/index.js위 내용을 참고해서코드를 작성해보았습니다.import { useDispatch, useSelector } from "react-redux"; import AppLayout from "../components/AppLayout"; import PostForm from "../components/PostForm"; import PostCard from "../components/PostCard"; import { useEffect } from "react"; import { loadPostsRequestAction } from "../reducers/post"; import { loadMyInfoRequestAction } from "../reducers/user"; import wrapper from "../store/configurStore"; import axios from "axios"; // 프론트, 브라우저 같이 실행 const Home = () => { const { me } = useSelector((state) => state.user); const { mainPosts, hasMorePosts, loadPostsLoading, retweetError } = useSelector((state) => state.post); const dispatch = useDispatch(); useEffect(() => { if (retweetError) { alert(retweetError); } }, [retweetError]); useEffect(() => { const onScroll = () => { if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) { if (hasMorePosts && !loadPostsLoading) { const lastId = mainPosts[mainPosts.length - 1]?.id; dispatch(loadPostsRequestAction({ lastId, limit: 10 })); } } }; window.addEventListener("scroll", onScroll); return () => { window.removeEventListener("scroll", onScroll); }; }, [hasMorePosts, loadPostsLoading, mainPosts]); return ( <AppLayout> {me && <PostForm />} {/* 순서가 바뀌거나 삭제될 수 있는 리스트들에 key값으로 index를 쓰면 안됀다. */} {/* 반복문이 있고 바뀌지 않는 리스트일 경우에만 사용해도 된다. */} {mainPosts.map((post) => ( <PostCard key={post.id} post={post} /> ))} </AppLayout> ); }; export const getServerSideProps = wrapper.getServerSideProps((store) => async ({ req }) => { const cookie = req ? req.headers.cookie : ""; axios.defaults.headers.Cookie = ""; if (req && cookie) { axios.defaults.headers.Cookie = cookie; } await store.dispatch(loadPostsRequestAction()); await store.dispatch(loadMyInfoRequestAction()); console.log("state", store.getState()); }); export default Home; 그런데 await에서 await' has no effect on the type of this expression.ts(80007이런 경고문 때문인지이전 영상에서 말씀해주셨던것처럼 데이터를 success까지 기다리지 않는건지,빈값들만 오고 있습니다.혹시 어디를 살펴보면 좋을지 알 수 있을까요?{ "name": "react-nodebird", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }, "author": "", "license": "MIT", "dependencies": { "@ant-design/icons": "^5.3.6", "@reduxjs/toolkit": "^2.2.3", "antd": "^5.8.3", "axios": "^1.6.8", "next": "^14.2.3", "next-redux-wrapper": "^8.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.51.3", "react-redux": "^9.1.1", "react-slick": "^0.30.2", "redux": "^5.0.1", "shortid": "^2.2.16", "styled-components": "^6.1.8" }, "devDependencies": { "@faker-js/faker": "^8.4.1", "babel-eslint": "^10.1.0", "eslint": "^8.57.0", "eslint-config-airbnb": "^19.0.4", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.2" } } 환경은 위와같습니다.감사합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
다른 페이지갔다가 오면 게시글 불러와지는 이슈
게시글을 끝까지 스크롤하고,다른 페이지에 다녀오면다른 페이지갔다가 오면 게시글 불러와지는 이슈를 발견했습니다.index.js에서 게시글 불러오는 부분에mainposts의 조건을 붙여서 실행하면될거같은데차후 강의에서 해결해주시는 이슈인지 궁금합니다.아니면 저만 그런건지 여쭤봅니다!감사합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
도메인과 S3연결 후 AWS 요금 과금문제 문의드립니다.
강의를 보고 따라하다가 오늘 보니 저번 달 사용량에 관련된 요금이 결제되었다고 메일을 받았습니다.. Amazon Virtual Private Cloud 에서 좀 많이 나왔고,,Amazon Route 53에도 요금이 나왔네요..총 만원정도 과금이 되었는데 어떤건지 몰라서 어디부분을 삭제해야 비용이 발생하지 않는걸까요?강의를 잘 보고 따라했는데.. 당황스럽습니다...
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
회원가입하고나서 로그인 풀리는 현상
안녕하세요회원가입 후에 로그인이 된 상태여야 하는데로그인이 풀리는 현상을 확인했습니다. 왜 인지 찾아보니프론트에선 회원가입 후 더미데이터를 가지고 로그인 하고 있었음백단에서는 회원가입 유저의 정보를 내려주지 않고 있었음.이런 경우때문인 것 같았습니다.그래서 회원가입 후 메인 페이지로 라우팅 되면메인페이지에서 로그인 유무를 판단하는 로직이 실행되고,더미데이터로 있던 데이터를 로그인 풀어버려서 그런게 아닐까 합니다. 그러면/back/routes/user.jsconst user = await User.create({ email, nickname, password: hashedPassword, }); 여기서 user정보를 내려줘야할거 같은데여기서 더 필요한 post, image같은 이미지는 어떻게 추가해야하는지 궁금합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
센셕4 게시물 불러오기 postcard.js에서 post.User.nickname[0]에러
안녕하세요센셕4 게시물 불러오기 postcard.js에서 post.User.nickname[0]에러가 발생해서 처음엔 간단하게/components/postcard.js<Card.Meta avatar={<Avatar>{post.User?.nickname[0]}</Avatar>} title={post.User?.nickname} description={<PostCardContent postData={post.content} />} />이런식으로 해결했었습니다.아 같은 파일 위치에서{id && post.User?.id === id ? ( <> <Button type='primary' key='modify'> 수정 </Button> <Button type='danger' key={"delete"} onClick={onRemovePost} loading={removePostLoading}> 삭제 </Button> </> ) : ( <Button type='dashed' key={"report"}> 신고 </Button> )}post.User?.id이것도 같은 식으로 처리했었습니다.그런데 제로초님 코딩을 몇번 다시봤더니비슷한 에러가 코멘트에서 났었는데/routes/posts.jsconst express = require("express"); const router = express.Router(); const { Post, User, Image, Comment } = require("../models"); // GET /posts 여러 게시글 가져오기 router.get("/", async (req, res, next) => { try { const posts = await Post.findAll({ limit: 10, include: [ { model: User, attributes: ["id", "nickname"], }, { model: Image, }, { model: Comment, include: [ { model: User, attributes: ["id", "nickname"], }, ], }, ], }); res.status(200).json(posts); } catch (error) { console.error(error); next(error); } }); module.exports = router; 이런식으로 데이 필요한 id, nickname을 넣어주셔서 ?를 붙이지 않고 해결하셨더라구요.혹시 저 nickname부분도 위와같이 백단에서 코드를 수정해서 고칠수 있을까요?따라서 해봤는데 잘 안되서 여쭤봅니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
로그인 기능을 next-auth와 수업에서 처럼 직접 구현하는 것 어떤걸 더 추천하시나요?
안녕하세요 제로초님강의를 따라가다 보니 로그인 부분이 엄청 복잡하더라구요,개인적으로 next-auth을 사용하면 되게 간편했던걸로 기억하는데 제로초님은로그인 기능을 만들게 된다면 next-auth와 수업에서 처럼 직접 구현하는 것 어떤걸 더 추천하실지 궁금합니다.직접구현하는게 로그인이 어케 이뤄지는지 이해할수있어서 좋은거같은데 여쭤보고 싶었습니다!
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
redux toolkit, saga으로 하는중에 devtool에서 액션 명칭이 undefined로 나옵니다
안녕하세요 제로초님여쭤보고 싶은게 있습니다.강의를 따라가다가 데브툴에서 액션, 데이터들을 확인하시는 모습을 봤는데,저도 따라해보니 액션명칭도, state tree의 데이터들도 제대로 안나오는것 같더라구요.그래서 좀 테스트해보니까아래의 로직처럼put에 액션 함수를 실행하면 데브툴에서 액션 명칭니 나오고예시: put(액션명(데이터)),기존처럼put({type: 액션명, payload: 데이터}) 호출하게되면undefined로 나오는것 같습니다.function* addPost(action) { try { yield delay(1000); const id = shortid.generate(); yield put(addPostSuccessAction({ id, content: action.payload.text, }) ); yield put({ type: addPostToMe, payload: id }); } catch (err) { yield put({ type: addPostFailureAction, error: err.response.data }); } }아무래도 toolkit과 saga를 사용할때 저렇게 사용하는게 아닌가 싶은데undefined로 나오는 부분은 개선했다고 하더라도action 부분이 제로초님과 다르게 나오더라구요.액션명만 나오는게 아니라 reducer의 위치도 함께나온느데이렇게 나와도 괜찮은건지 여쭙고 싶습니다.다른 부분은 잘 작동하는것 같습니다. import { HYDRATE } from "next-redux-wrapper"; import { createSlice } from "@reduxjs/toolkit"; import shortId from "shortid"; export const initialState = { mainPosts: [ { id: "1", User: { id: "1", nickname: "zzimzzim", }, content: "첫 번째 게시글 #해시태그, #익스프레스", Images: [ { id: shortId.generate(), src: "https://loremflickr.com/cache/resized/65535_53669042936_630c778818_320_240_nofilter.jpg", }, { id: shortId.generate(), src: "https://loremflickr.com/cache/resized/65535_52982053835_12fc661207_320_240_nofilter.jpg", }, { id: shortId.generate(), src: "https://loremflickr.com/cache/resized/65535_52905479084_303bf25ec0_320_240_nofilter.jpg", }, ], Comments: [ { id: shortId.generate(), User: { nickname: "찜찜", }, content: "얼른 사고싶어요~", }, { id: shortId.generate(), User: { nickname: "hero", }, content: "리액트 넥스트 고수가 될테다~", }, ], createdAt: {}, }, ], imagePaths: [], addPostLoading: false, addPostDone: false, addPostError: null, addCommentLoading: false, addCommentDone: false, addCommentError: null, }; const dummyPost = ({ id, content }) => { return { id, content, User: { id: "1", nickname: "WlaWla", }, Images: [], Comments: [], }; }; const dummyComment = (content) => ({ id: shortId.generate(), content: content, User: { id: "1", nickname: "WlaWla", }, }); const postSlice = createSlice({ name: "post", initialState, reducers: { addPostRequestAction: (state, action) => { state.addPostLoading = true; state.addPostDone = false; state.addPostError = null; }, addPostSuccessAction: (state, action) => { state.addPostLoading = false; state.addPostDone = true; state.mainPosts.unshift(dummyPost(action.payload)); }, addPostFailureAction: (state, action) => { state.addPostLoading = false; state.addPostError = action.error; }, addCommentRequestAction: (state, action) => { state.addCommentLoading = true; state.addCommentDone = false; state.addCommentError = null; }, addCommentSuccessAction: (state, action) => { const { content, userId, postId } = action.payload; const post = state.mainPosts.find((d) => d.id === postId); post.Comments.unshift(dummyComment(content)); state.addCommentLoading = false; state.addCommentDone = true; }, addCommentFailureAction: (state, action) => { state.addCommentLoading = false; state.addCommentError = action.error; }, }, extraReducers: (builder) => builder .addCase(HYDRATE, (state, action) => ({ ...state, ...action.payload.post, })) .addDefaultCase((state) => state), }); export const { addPostRequestAction, addPostSuccessAction, addPostFailureAction, addCommentRequestAction, addCommentSuccessAction, addCommentFailureAction } = postSlice.actions; export default postSlice.reducer; 사가 코드입니다.import { all, fork, delay, put, takeLatest } from "redux-saga/effects"; import axios from "axios"; import { addPostRequestAction, addPostSuccessAction, addPostFailureAction, addCommentRequestAction, addCommentSuccessAction, addCommentFailureAction } from "../reducers/post"; import { addPostToMe, removePostOfMe } 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); console.log("addPost saga", action); yield delay(1000); const id = shortid.generate(); yield put( addPostSuccessAction({ id, content: action.payload.text, }) ); yield put({ type: addPostToMe, payload: id }); } catch (err) { yield put({ type: addPostFailureAction, error: err.response.data }); } } function addCommentApi(data) { return axios.post(`/api/post/${data.postId}/comment`); } function* addComment(action) { try { // const result = yield call(addPostAPI, action.data); console.log("addComment saga", action); yield delay(1000); yield put({ type: addCommentSuccessAction, payload: action.payload, }); } catch (err) { console.log(err); yield put({ type: addCommentFailureAction, error: err.response.data, }); } } function* watchAddPost() { yield takeLatest(addPostRequestAction, addPost); } function* watchAddComment() { yield takeLatest(addCommentRequestAction, addComment); } export default function* postSaga() { yield all([fork(watchAddPost), fork(watchAddComment)]); }
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
제로쵸 선생님 ㅜ 리액트 문제는 아닌데 질문드려도 될까요?ㅜ
제로쵸 님의 강의를 여러개 듣고 있는 코딩 입문자입니다 ㅜ간단한 사진첩 클릭 사이트를 만들었는데요.스샷처럼 한쪽 사진만 계속 선택 했을 때는 함수가 한번만 실행되며 제가 원하는 아웃풋이 나오는데,다른 사진을 선택하면 함수가 여러번 실행이 되네요... 왜이러는걸까요? ㅜㅜ소스는 html+js+부트스트랩 로 짰습니다. 원본소스의 링크는 아래 올려놨습니다.https://drive.google.com/file/d/1Ov0Qik9ofDHli-TsblLw5OkUz7uCXTDo/view?usp=drive_link
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
toolkit, saga를 사용하는데 saga에서 로그인정보 받을때 state가 이전 state가 아니라 proxy데이터가 나옵니다.
안녕하세요 제로초님toolkit, saga를 사용하는데 saga에서 로그인정보 받을때 state가 이전 state가 아니라 proxy데이터가 나옵니다. saga에서 이런식으로 호출을 해주면action 데이터도 잘 찍힙니다.function* login(action) { try { console.log("saga login"); console.log(action); // const result = yield call(loginAPI, action.data); yield delay(1000); yield put({ type: loginSuccessAction, payload: action.payload, }); } catch (err) { yield put({ type: loginFailureAction, // data: err.response.data, }); } }그다음 리듀서에서const userSlice = createSlice({ name: "user", initialState, reducers: { logIn: (state, action) => { state.isLoggedIn = true; state.me = action.payload; }, logOut: (state) => { state.isLoggedIn = false; state.me = null; }, loginRequestAction: (state) => { console.log("state", state); state.isLoggingIn = true; }, loginSuccessAction: (state, action) => { console.log("reducer login"); console.log("state", state); state.isLoggingIn = false; state.isLoggedIn = false; state.me = action.payload; state.me.nickname = "zzimzzim"; }, loginFailureAction: (state) => { state.isLoggingIn = false; state.isLoggedIn = false; }, logoutRequestAction: (state) => { state.isLoggingOut = true; }, logoutSuccessAction: (state) => { state.isLoggingOut = false; state.isLoggedIn = false; state.me = null; }, logoutFailureAction: (state) => { state.isLoggingOut = false; }, }, extraReducers: (builder) => builder .addCase(HYDRATE, (state, action) => ({ ...state, ...action.payload.user, })) .addDefaultCase((state) => state), }); 위와같이 코드를 만들고 로그인을 해보면action으로 로그인 정보를 잘 내려줍니다.원래는 이전의 state (현 store데이터)를 받아오고거기서 action데이터를 넣어서 업데이트해줘야하는데,state에서 proxy데이터가 내려와서 업데이트가 안되고 있습니다. ㅠ 리듀서에서 action 데이터 받고, state호출했을때,Proxy(Object) {type_: 0, scope_: {…}, modified_: false, finalized_: false, assigned_: {…}, …}[[Handler]]: null[[Target]]: null[[IsRevoked]]: true이러한 경우에 reducer에서 state를 받아오는 방법이 있을까요? 혹시 코드가 이상하거나 틀렸다면 지적해주시면 감사하겠습니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
redux toolkit을 사용하시면서 redux saga를 사용하시다가 애먹으시는 분들은 참고해주세요
https://choisuhyeok.tistory.com/57 요기에 잘 정리 되어있어서제로초님 강의 보면서 툴킷으로만 작업하거나,thunk로 하지 않아도,toolkit + saga로 작업할 수 있습니다.참고 부탁드립니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
깃헙에 saga가 아니라 createAsyncThunk로 작업이 되어있으신거같아서 궁금해서 여쭤봅니다.
안녕하세요 제로초님깃헙에 saga가 아니라 createAsyncThunk로 작업이 되어있으신거같아서 궁금해서 여쭤봅니다.깃헙에는 saga를 대체해서 toolkit에 내장되어있는 redux-thunk를 사용하신걸까요? 그렇다면saga내용을 다 보고 thunk의 모양대로 수정해서 진행하게 되면 될지 궁금합니다.감사합니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
antd ui라이브러리를 사용하니까 Cannot use import statement outside a module 에러가 나옵니다.
안녕하세요 제로초님antd 라이브러리를 사용하니까Server ErrorSyntaxError: Cannot use import statement outside a moduleThis error happened while generating the page. Any console logs will be displayed in the terminal window.Call Stack<unknown>file:///C:/Users/%EC%A0%95%EC%9A%A9%EC%B1%84/Desktop/%EA%B3%B5%EB%B6%80/%EA%B0%95%EC%9D%98/react-nodebird/prepare/front/node_modules/rc-util/es/hooks/useMemo.js (1)Next.jsrc-util/es/hooks/useMemofile:///C:/Users/%EC%A0%95%EC%9A%A9%EC%B1%84/Desktop/%EA%B3%B5%EB%B6%80/%EA%B0%95%EC%9D%98/react-nodebird/prepare/front/.next/server/pages/index.js (8862:18)Next.jseval/node_modules/antd/es/config-provider/index.js./node_modules/antd/es/config-provider/index.jsfile:///C:/Users/%EC%A0%95%EC%9A%A9%EC%B1%84/Desktop/%EA%B3%B5%EB%B6%80/%EA%B0%95%EC%9D%98/react-nodebird/prepare/front/.next/server/vendor-chunks/antd.js (570:1)Next.jseval/node_modules/antd/es/grid/col.js 이러한 에러들이 나왔고 검색해보니next.config.js 파일에const nextConfig = { /* config options here */ // reactStrictMode: true, swcMinify: true, transpilePackages: [ "antd", "@ant-design", "rc-util", "rc-pagination", "rc-picker", "rc-notification", "rc-tooltip", ], compiler: { styledComponents: true, }, }; module.exports = nextConfig;이런식으로 넣어주면 괜찮다는 것을 보고실행해보니 정상적으로 나오는것을 확인했습니다.그런데 이제는 Warning: findDOMNode is deprecated and will be removed in the next major release. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here:이런 에러가 나오고 있는데요..원래 antd최신버전을 쓰고 있다가깃헙의 제로초님과 동일하게 ^5.8.3" 버전으로 수정해봤는데도 동일한 이슈가 발생하더라구요혹시 다른 부분을 수정해야할지 알 수 있을까해서차장보다 문의 드립니다.참고로 현재 package.json은{ "name": "react-nodebird", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }, "author": "", "license": "MIT", "dependencies": { "@ant-design/icons": "^5.3.6", "@reduxjs/toolkit": "^2.2.3", "antd": "^5.8.3", "next": "^14.2.3", "next-redux-wrapper": "^8.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.51.3", "react-redux": "^9.1.1", "react-slick": "^0.30.2", "redux": "^5.0.1", "styled-components": "^6.1.8" }, "devDependencies": { "eslint": "^8.57.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.2" } } 이렇습니다.감사합니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
어느 순간부터 vs코드에서 저장한 내용이 브라우저에서 적용이 안되고있습니다.
안녕하세요 제로초님어느 순간부터 vs코드에서 저장한 내용이 브라우저에서 적용이 안되고있습니다.새로고침해도 수정한 로직이 적용되지 않은채여서,처음에는 일시적인 에러인가 해서 그냥 ctr + c 로 껐다가다시 npm run dev로 켜서 수정된것 브라우저에서 확인하고 했는데 이게 지속적으로 진행되다보니 어떤 문제가 있는건지도 모르겠고 답답해서 여쭤봅니다.ㅠ노드모듈, .next파일 을 지웠다 다시 설치해보기도 해도 여전히 그대로라 문의 올려봅니다 ㅠ어떤것 때문인지 짐작도 안가서 코드를 못 올리는점 참고부탁드립니다. 제로초님 코딩으 따라가면서 잘됐는데 이부분만 왜 갑자기 이러는지..;;
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
도메인을 연결하고 프론트 도메인으로 접속 로딩시간이 엄청 오래걸립니다.
도메인을 연결하고 프론트 도메인으로 접속 로딩시간이 엄청 오래걸립니다.이런경우는 도대체 뭐때문에 그럴까요? 용량이 커서 그럴까요?백 서버 도메인은 접속이 잘됩니다..vscode에서도 front터미널만 자꾸 렉걸리는 현상이 생겨요 ㅠㅠ
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
nginx+https 적용후 www.zoommeetinggo.p-e.kr 입력시 서비스 화면이 아닌 Welcome to nginx 화면 이동 관련하여 질문드립니다.
nginx+https 적용후www.zoommeetinggo.p-e.kr 입력시 서비스 화면이 아닌 Welcome to nginx 화면으로 이동하게 되었습니다. (https 가 적용된 https://www.zoommeetinggo.p-e.kr 으로 이동)- sudo vim /etc/nginx/nginx.conf 코드 수정# 443 포트로 접근시 ssl을 적용한 뒤 3000포트로 요청을 전달해주도록 하는 설정server {server_name 13.125.232.127 www.zoommeetinggo.p-e.kr; listen 80; location / { proxy_set_header HOST $host; proxy_pass http://127.0.0.1:3000; proxy_redirect off; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/www.zoommeetinggo.p-e.kr/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/www.zoommeetinggo.p-e.kr/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } # 80 포트로 접근시 443 포트로 리다이렉트 시켜주는 설정 server { if ($host = www.zoommeetinggo.p-e.kr) { return 301 https://$host$request_uri; } # managed by Certbot server_name www.zoommeetinggo.p-e.kr; listen 80; return 404; # managed by Certbot }///////////////////////////////////////////////////////////////////////- sudo vim cd /etc/nginx/sites-available/default 코드 수정server { listen 80; listen [::]:80; root /var/www/html index index.html index.htm index.nginx-debian.html; server_name www.zoommeetinggo.p-e.kr; location / { proxy_pass http://localhost:3000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; }}코드 수정후http://13.125.232.127 연결시 http://13.125.232.127:3000 리다이렉트 하여 서비스 화면을 보여줍니다. (http)http://www.zoommeetinggo.p-e.kr:3000 연결시 서비스 화면 보여줍니다, (http)https://www.zoommeetinggo.p-e.kr 연결시 nginx 화면으로 이동됩니다.무엇을 놓치고 있는지 잘 모르겠어서 질문드립니다. (https://www.zoommeetinggo.p-e.kr/ 연결시 https://www.zoommeetinggo.p-e.kr:3000 포트로 연결 되게 하고 싶습니다.)
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
reducer설정 중 user 초기값이 undefined로 나오는 에러
안녕하십니까 형님진행하다 보니 궁금한게 생겨서 여쭤보려고 합니다. reducer설정 중 초기값이 undefined로 들어오는 에러가 놔왔었는데욥error - Error: The slice reducer for key "user" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined. 에러 났던 부분운 default: state 이 부분이고 코드 보고 아래처럼 수정하니 잘 동작했습니다. export const initialState = { isLoggedIn: false, user: null, signUpData: {}, loginData: {}, } const reducer = (state=initialState, action) => { switch(action.type) { default: { return { ...state } } } }default: state vs default: { return {...state}}문법적으로 똑같은 내용이 아닌지...어떤 차이가 있는지를 모르겠어서 질문드립니다!