월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 해결됨[리뉴얼] 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;
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
redux thunk 원리 질문
1.thunk를 사용하면 함수형 action을 dispatch 했을때action을 단순히 실행하는 것으로 보이는데요. 그렇다면 아래 두 코드처럼함수형 action을 dispatch하는 방식과 함수로 감싸지 않고 하는 방식과 결과는 같다고 생각되는데 맞나요?const loginAction = () => { return (dispatch) => { dispatch(loginRequestAction()); axios.post('/api/login') .then((res) => { dispatch(loginSuccessAction(res.data)); }) .catch((err) => { dispatch(loginFailureAction(err)); }) } } const onClickLogin = () => { dispatch(loginAction()); }const onClickLogin = () => { dispatch(loginRequestAction()); axios.post('/api/login') .then((res) => { dispatch(loginSuccessAction(res.data)); }) .catch((err) => { dispatch(loginFailureAction(err)); }) }2.1이 맞다면 함수 action을 dispatch하는 방식은 편의성 때문이라고 봐도 될까요?3.강의 10:40 쯤에 thunk는 한번에 dispatch를 여러번 할 수 있게 해준다고 하셨는데thunk없이 아래처럼 여러번 쓰는 것은 문제가 될 수 있나요?// action은 임의로 지었습니다 const onClickButton = () => { dispatch({type: 'CHANGE_ID'}); dispatch({type: 'CHANGE_PASSWORD'}); }
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
sequlize같은 orm에 대한 질문
javascript는 sequilize java는 JPA같은 데이터베이스를 다루는 ORM이 있는 것 같은데 ORM에 대해 좀더 찾아보니 sql 쿼리문을 직접 작성하는 것보다 코드의 양도 줄고 유지 보수도 더 쉬운 것 같습니다.나중에 sql 쿼리문을 직접 입력하는 방식을 배워야하나 생각이 들었지만 이렇게 장점이 많은 ORM을 안 쓸 이유가 없는 것 같고 나중에 다른 백앤드 프레임 워크 예를 들어 java Spring같은 것을 배워도 JPA같은 ORM을 배우지 sql 쿼리문을 직접 넣는 방식으로는 하지 않을꺼 같습니다.그럼에도 불구하고 sql 쿼리문을 직접 넣는 방식을 배워둘 필요가 있을까요? 아니면 ORM방식에 단점이 있을까요?
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
state is not defined 에러가 뜹니다
이 에러는 왜 발생한 건가요?구글에 검색도 해봤는데 안나오네요도와주세요 ㅠreducers/user.js 코드입니다export const initialState = { } export const loginAction = (data) => { return { type: 'LOG_IN', data, } } export const logoutAction = () => { return { type: 'LOG_OUT', } } const reducer = (State = initialState, action) => { switch (action.type) { case 'LOG_IN': { return { ...state, isLoggedIn: true, user: action.data, }; } case 'LOG_OUT': { return { ...state, isLoggedIn: false, user: null, }; } default: return state; } }; export default reducer; 터미널창 오류 메세지 입니다error - reducers/user.js (37:12) @ reducererror - ReferenceError: state is not defined at reducer (webpack-internal:///./reducers/user.js:39:13) 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:15:75) at Object../reducers/index.js (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/.next/server/pages/_app.js:33:1) at __webpack_require__ (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/.next/server/webpack-runtime.js:33:42) at eval (webpack-internal:///./store/configureStore.js:9:67) at Object../store/configureStore.js (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/.next/server/pages/_app.js:66:1) { page: '/'} 35 | } 36 | default:> 37 | return state; | ^ 38 | 39 | } 40 | };event - compiled client and server successfully in 100 ms (1518 modules)4. WrappedApp created new store with withRedux(NodeBird) { initialState: undefined, initialStateFromGSPorGSSR: undefined }ReferenceError: state is not defined at reducer (webpack-internal:///./reducers/user.js:39:13) 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:15:75) at Object../reducers/index.js (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/.next/server/pages/_app.js:33:1) at __webpack_require__ (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/.next/server/webpack-runtime.js:33:42) at eval (webpack-internal:///./store/configureStore.js:9:67) at Object../store/configureStore.js (/Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/.next/server/pages/_app.js:66:1) http://localhost:3060 에 뜬 오류 입니다 Server ErrorReferenceError: state is not definedThis error happened while generating the page. Any console logs will be displayed in the terminal window.Sourcereducers/user.js (35:12) @ reducer 33 | } 34 | default: > 35 | return state; | ^ 36 | 37 | } 38 | };
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
(해결/오타문제) addPost 액션이 동작하지 않는 것에 대해 궁금합니다.
안녕하세요. 항상 좋은 강의를 해주셔서 감사합니다.다름이 아니라 글 작성시 콘솔에 ADD_POST_REQUEST에 대한 리듀서 작동은 확인했으나 그 뒤에 addPost 사가가 작동하지 않는 것에 대해 질문이 있습니다.위 문제는 watchAddPost()가 정상적으로 ADD_POST_REQUEST에 대한 이벤트를 캐치하지 못 해서 다음 과정이 진행되지 않는 것이라고 생각합니다. 다만 콘솔에서는 이에 대한 것도 뜨지 않아 해당 오류를 해결하기 어려워 질문 드립니다.다음은 제가 작성한 코드입니다. ()@/component/PostFrom.jsconst PostForm = () => { const dispatch = useDispatch(); const { imagePaths, addPostDone } = useSelector((state) => state.post); const [text, onChangeText, setText] = useInput(""); const onSubmit = useCallback(() => { dispatch(addPostRequestAction(text)); }, [text]); ... }@/reducer/post.jsexport const initialState = { mainPosts: [...], ... addPostLoading: false, addPostDone: false, addPostError: 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 addPostRequestAction = (data) => ({ type: ADD_POST_REQUEST, data, }); const dummyPost = (data) => ({ id: 2, content: data, User: { id: 1, nickname: "윤섭", }, Images: [], Comments: [], }); const reducer = (state: initialState, action) => { switch (action.type) { case ADD_POST_REQUEST: console.log("REDUCER: ADD_POST_REQUEST"); return { ...state, addPostLoading: true, addPostDone: false, addPostError: null, }; case ADD_POST_SUCCESS: console.log("REDUCER: ADD_POST_SUCCESS"); return { ...state, mainPosts: [dummyPost(action.data), ...state.mainPosts], addPostLoading: false, addPostDone: true, }; ... } }@/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, } from "@/reducers/post"; // add post function addPostAPI(data) { return axios.post("/api/post"); } function* addPost(action) { try { // const result = yield call(addPostAPI); yield delay(1000); console.log("SAGA: addPost"); yield put({ type: ADD_POST_SUCCESS, data: action.data, }); } catch (err) { yield put({ type: ADD_POST_FAILURE, error: err.response.data, }); } } function* watchAddPost() { yield takeLatest(ADD_POST_REQUEST, addPost); } export default function* postSaga() { yield all([fork(watchAddPost), fork(watchAddComment)]); }아래는 ADD_POST_REQUEST 이후 아무런 반응이 없는 스크린샷입니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
npm run dev 오류가 뜹니다
http://localhost:3000/에 뜬 오류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.Sourcecomponents/AppLayout.js (15:52) @ eval 13 | 14 | const AppLayout = ({ children }) => { > 15 | const isLoggedIn = useSelector((state) => state.user.isLoggedIn); | ^ 16 | // const { isLoggedIn } = useSelector(state => state.user); 17 | 18 | return (Call StackAppLayoutcomponents/AppLayout.js (15:36)Show collapsed frames 터미널에 뜬 오류TypeError: Cannot read properties of undefined (reading 'user')at eval (webpack-internal:///./components/AppLayout.js:32:101)at /Users/hyeonyeongjeong/Documents/2023project/nodebird/prepare/front/node_modules/react-redux/lib/hooks/useSelector.js:67:26 npm run dev를 하고http://localhost:3000/ 들어갔더니이런 오류가 뜨면서 안됩니다.유저를 왜 읽지 못하는 걸까요?오류 해결하고 싶습니다 ㅠ
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
createAsyncThunk 진행 순서
제가 이번에 loadPosts를 createAsyncThunk로 구현하면서 createAsyncThunk의 진행 순서가 궁금해서 질문 드립니index.js useEffect(() => { console.log("dipathch hi"); dispatch(loadPosts(10)); }, [dispatch]);reducers/post.js export const loadPosts = createAsyncThunk(LOAD_POST, async (data) => { trottle(); return data; }); const trottle = () => listenerMiddleware.startListening({ type: LOAD_POST, effect: async (action, listenerApi) => { listenerApi.unsubscribe(); console.log("Original state ", listenerApi.getOriginalState()); await listenerApi.delay(5000); console.log("Current state ", listenerApi.getState()); listenerApi.subscribe(); }, }); const postSlice = createSlice({ name: "post", initialState, extraReducers: (builder) => builder .addCase([HYDRATE], (state, action) => ({ ...state, ...action.payload.post, })) // loadPosts .addCase(loadPosts.pending, (state, action) => { state.loadPostsLoading = true; state.loadPostsDone = false; }) .addCase(loadPosts.fulfilled, (state, action) => { action.payload = generateDummpyPost(action.payload); state.mainPosts = action.payload.concat(state.mainPosts); state.hasMorePost = state.mainPosts.length < 50; state.loadPostsLoading = false; state.loadPostsDone = true; }) .addCase(loadPosts.rejected, (state, action) => { state.loadPostsLoading = false; state.loadPostsError = action.error; }) .addDefaultCase((state) => state), });제가 하나하나 console.log를 찍어서 확인한 진행 순서를 얘기해 드리겠습니다일단 화면을 처음 랜더링할때 index.js에서 dispatch가 제일 먼저 실행됩니다 그리고 post.js로 넘어와서 loadPost.pending -> loadPosts.fulfilled ->loadPosts = createAsyncThunk(LOAD_POST) 이 순서대로 진행이 됩니다 그래서 loadPosts.fulfilled에 generateDummpyPost()함수를 작성한 것입니다그래서 마지막에 createAsyncThunk가 실행이 되니 return이 필요없지 않나? 라는 생각에 return을 지워봤더니 post가 하나만 작성되고 그 이후는 작성되지 않았습니다제가 궁금한 부분은 세개입니다createAsyncThunk의 정확한 진행순서가 궁금합니다generateDummpyPost()함수를 저렇게 작성하는게 맞는지 궁금합니다https://blog.logrocket.com/redux-toolkits-new-listener-middleware-vs-redux-saga/ 에서 Throttling관련 얘기가 있어서 똑같이 따라 해봤는데 쓰로틀링을 createAsyncThunk에 장착하는 방법을 잘 모르겠습니다 쓰로틀링이 없어도 윈도우 이밴트로 요청이 한번에 많이 오는 현상은 없지만 그래도 궁금해서 질문 드립니다
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
redux toolkit을 사용해서 throttling설절을 어떻게 할 수 있나요?
아래 질문(reduxjs/toolkit 에서 loadPost 한번만 가게하기)에서 비슷한 내용의 질문이 올라왔는데delay란 함수를 어떻게 설정하는지 잘 이해가 안되서 질문드립니다
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
useEffect()의 window에 관한 콘솔은 계속 찍히는 이유가 무었인가요?
useEffect(() => { function onScroll() { console.log( window.scrollY, document.documentElement.clientHeight, document.documentElement.scrollHeight ); if ( window.scrollY + document.documentElement.clientHeight === document.documentElement.scrollHeight ) if (hasMorePost) { const dummypost = generateDummpyPost(10); dispatch(loadPost({ dummypost })); } } window.addEventListener("scroll", onScroll); return () => { window.removeEventListener("scroll", onScroll); }; }, [hasMorePost]);useEffect를 이런 식으로 작성하면 hasMorePost의 값이 변경되기 전까지 useEffect는 처음 단한번만 실행된후 그후에는실행되지 않아야 하는데 왜 계속 console.log가 실행되는건가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
댓글부분 구현시 Comment가 적용되지 않는 부분
댓글 구현하기 수강 중에 제로초님께서 antd의 <Comment />를 import 하여 적용하는데 안 되시는 분들은 List.Item과 List.Item.Meta를 이용해서 만들어보세요. 제 경우 antd를 최신버전으로 사용하고 있는데 공식문서를 보니 Comment는 없습니다. 대신 List 컴포넌트에 List.Item과 List.Item.Meta가 있는데 이걸 이용해 댓글 컴포넌트를 만들 수 있습니다. {commentFormOpened && ( <div> <CommentForm post={post} /> <List header={`${post.Comments.length}개의 댓글`} itemLayout="horizontal" dataSource={post.Comments} renderItem={(item) => ( // 강의에서는 <li></li>로 사용하고 있습니다. <List.Item> {/* 강의에서는 <Comment />로 사용하고 있습니다. */} <List.Item.Meta // author => title로, content => description으로 적용하면 강의에서 보는 UI와 똑같아요. title={item.User.nickname} avatar={<Avatar>{item.User.nickname[0]}</Avatar>} description={item.content} /> </List.Item> )} /> </div> )}
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
rangepicker를 쓰려고 하는데요
레인지피커의 값을 받아오려는데 e.target.value를 하면 Cannot read properties of undefined (reading 'value')이런 오류가 떠요구글링도 해봤는데 어떻게 알수 있는 방법 없을까요?레인지피커는 파란색 박스에요
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
antdesign 적용안됨 오류
안녕하세요 제로초님 강의 잘 듣고 있습니다 .제가 댓글 구현하기까지 잘 진행하던중 Comment가 antd에서 지원을 안해준다는 error가 발생하여 antd 버전을 강의와 맞게 다운그레이드 (npm i antd@4.16.1) 했습니다.근데 그 때 이후로 화면에 antd가 제대로 적용이 되지않은 상태가 되었습니다 . 다시 원래버전을 설치해도 같은 결과입니다 . Comment문제보단 화면에 antd가 다시 제대로 적용됬으면 합니다 . 혹시 해결방안이 있을까요?화면이 이런형식으로 바뀌였습니다. 코드는 지금까지 강의와 똑같이 진행하였습니다package.json
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
SSR을 적용해야하는 부분과 그렇지않은 부분에 대해
강좌에서 알려주신대로 페이지의 getServerSideProps로 초기로드를 SSR하는 방식을제 프로젝트에서도 적용을 했는데요. 모든 페이지에 SSR을 진행하다보니막상 배포해서 돌려보면 첫페이지 로드를 빨리 보여주는 장점보다,서버에서 렌더링을 기다려야하는 단점이 눈에 띄게 보였습니다.사실 모든 페이지에서 SSR을 적용하면 기존의 전통적인 웹과 다를바가 없지않나라는생각들 들고, SEO vs SPA 둘 중의 하나의 고민으로 오게되는데 제로초님은 이런경우에어떤 기준으로 판단을 하시나요?getStaticProps() 가 SSG(static site generation)에 해당하고 getServerSideProps() 가 SSR(server side rendering)에 해당하는것이 맞나요?
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
addPostToMe createSlice()로 처리하기
제가 redux toolkit으로 제로초님 강의를 따라가고 있는데 제로초님이 올려주신 toolkit 깃허브 코드에는 addPostToMe에 대해 reducers로 선언만 다른 코드에서 사용하는건 안보이는데 그래서 제가 강의내용과 비슷하게 수정을 했습니다user.js const userSlice = createSlice({ name: "user", initialState, reducers: { addPostToMe(draft, action) { draft.me.Posts.unshift({ id: action.payload }); }, removePostOfMe(draft, action) { draft.me.Posts = draft.me.Posts.filter((v) => v.id !== action.payload); }, }, }) export const { addPostToMe, removePostOfMe } = userSlice.actions;PostForm.js import { addPostToMe } from "../reducers/user"; const onSubmit = useCallback(() => { const id = shortId.generate(); dispatch(addPost({ id, text })); dispatch(addPostToMe()); }, [text]);이렇게 코드를 수정했는데 혹시 제로초님은 다른 방식으로 addPostToMe를 사용했는데 제가 발견을 못한걸까요?아니면 제가 작성한 코드에 뭔가 다른 문제가 생길 수도 있나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
하위 요소에 특정 데이터를 넘길 시, 필요한 데이터만 특정해서 넘기기 vs 객체 형태로 크게 넘기기
안녕하세요, 강사님"댓글구현하기"편을 보던 중 기존 업무에서도 궁금했던 점이 생각나 질문 남깁니다. 예를들어 부모요소에서 하위요소에 post객체에 속한 id값 하나만 전달하고자 합니다.그럴 때 <하위요소 post = {post}/>와 <하위요소 postId = {post.id}/> 중에 어떤 방식을 선호하시나요?단, 이때 자식요소에서 post의 id외에는 필요가 없습니다. 개인적으로는 다른 사람도 저의 코드를 보고 이해할때 '아! 이 자식 요소에서는 post의 id만 사용하는 구나' 딱 알수 있기 때문에 <하위요소 postId = {post.id}/>가 더 좋을 것 같다 생각했습니다. 강사님의 의견 여쭤보고 싶습니다. 답변 기다리겠습니다.감사합니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
MySQL Workbench 예기치 못한 상황에 종료되었습니다. ( 버전문제 )
프론트 부분 회원가입한 후 router설정 까지 끝낸 후,회원정보가 잘 들어갔는지 확인하기 위해 Workbench에서 Users테이블을 클릭 했는데,,바로 예기치 않게 종료되었습니다.가 떴습니다.구글링 해본 결과 버전이 문제인데싹 - 지우고 다운그레이 하라고 하더라구요. 지금 버전이 8.0.32 인데몇으로 다운그레이 해야할까요 ? + 그 전까지는 잘 작동되던 워크벤치가 왜 갑자기 버전충돌이 일어난거죠 ?
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
redux toolkit payload data 질문
https://github.com/ZeroCho/react-nodebird/tree/master/toolkitredux toolkit으로 바꾸면서 제로초님 강의를 듣고있는데 .addCase(addComment.fulfilled, (draft, action) => { const post = draft.mainPosts.find((v) => v.id === action.data.PostId); console.log('draft', draft, 'post', post, 'Comments', post.Coments); post.Comments.unshift(action.data); draft.addCommentLoading = false; draft.addCommentDone = true; })이런 코드를 보면 action.data이렇게 action에서 data를 가져오는데 제가 redux devtools에서 봤을 때 action안에 값들은 action.payload에 저장이 되어있는거 같습니다 혹시 값들을 action.data로 저장하는 방법이 있나요?