월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
n:m 관계 정의 시 궁금점
n:m 관계 정의 시에 궁금한 점이 있어서 질문 남깁니다. 동적인 카테고리가 있는 게시판의 경우 유저가 해당 카테고리에 게시글을 쓰는 걸 구현하기 위해서 category 라는 테이블을 만들고 user가 category에 여러 게시글을 쓸 수 있고 여러 카테고리에 게시글을 쓸 수 있어서 n:m 관계라고 정의했습니다. user - board - category 이런 식으로 되어있을 때 board에서 user의 정보와 category의 정보가 필요할 때 각각을 가지고 올 수 있어야 하는데sequelize에서는 관계가 없기 때문에 가져올 수 없다고 하더군요 이런 경우에는 user-category n:m으로 관계를 정의 하는게 아닌 user-board n:1 board-category 1:n 으로 정의하는 것이 맞을까요??
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
정의 방법
강의 잘 보고 있습니다. : ) Sequelize 공식 문서를 보니까 영상과 동일한 스타일의 모델 정의 방법을 찾을 수가 없어서 공식 문서대로 다음과 같이 작성했는데 문제 없나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
a태그 noopener, noreferrer 필요한 이유
안녕하세요. 제로초님강의를 듣다, a태그에 noopener, noreferrer에 대해 말씀주셨는데, 해당 속성에 관해서 전에 정리한 글이 있어 공유 드립니다.https://nuhends.tistory.com/107
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
라우트 이동 및 새로고침시 각 컴포넌트별 state 유지
안녕하세요 제로초님 포트폴리오를 nextjs로 새로만들면서 작업중인데 라우트 이동 및 새로고침시에도 각 컴포넌트별 state를 유지하는 효율적인 방법을 좀 알고싶습니다. url : https://dv-node-portfolio.herokuapp.com/git : https://github.com/dvisign/new-next-portfolio전에 리액트로 할때는 useHistory를 이용한 커스텀 훅스 route state에 저장하였는데 nextjs에서는 사용할수가 없어서 useRouer로 저장이 가능한가 확인해보았는데 안되더라구요 그래서 다음으로 알아본것이 redux-persist 라이브러리였는데 리뉴얼된 강좌에서 ssr때문에 HYDRATE 액션때문에 사용은 해보지 않았고 리액트로 redux-persist 라이브러리를 사용했을때 로컬스토리지에 담는것같아 이용하지 않았습니다(개인적으로 localstorage를 신뢰하지 않는 개인적 성향때문에 그렇습니다.) 예를들어서 사이드 네비게이션이 열려있는것을 닫았다가 다른 라우트로 이동했을때 다시 열려있는 현상입니다. 스크린샷을 첨부합니다. 부단 이 부분 뿐만 아니라 나중에 더 만들다보면 이런 state유지 할것들이 더 많아질텐데 어떻게 유지 시키는것이 가장 효율적인 방법일까요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
상품 결제? 관계질문입니다.
안녕하세요 제로초님 유저가 결제한 목록을 db에 저장하려합니다. 결제 목록의 관계는 Cart와 User 간의 다대다 관계로 만들었습니다. ( 카트에 담긴 아이템에서 체크한 아이템들을 보내고 유저아이디와 함께 결제목록을 만들기 위해서) 유저가 결제 버튼을 누를때 Cart에서 체크 한 cart item id 값들을 백엔드로 배열로 보내고 이 값들을 payment table의 CartId에 등록하려합니다. (CartId와 UserId를 제외한 컬럼은 모두 payment table의 기본 값입니다.) payment table의 기본값을 가지고 배열로 받은 CartId의 값만 다르게 데이터를 여러 개를 만들고 싶은데 잘 되지 않습니다 . 아래와 같이 코드를 작성해보았습니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
serverSide Cookie 질문 드립니다. (axios interceptor)
안녕하세요. 강의 잘 듣고있습니다. 다름이 아니라 getServerSideRendering을 할 때 쿠키를 사용하여 사용자 정보를 얻는 로직을 처리하고 있는데 궁금증이 생겨서 질문드립니다. 우선 문제 상황은 axios interceptor를 사용하여 쿠키를 보낼 때 request Header에 쿠키값이 담기지 않는다는 것 입니다. 구글링 등 반나절 한거같은데 정확한 원인을 모르겠어서 질문드립니다.. 1. serverSideProps 쪽 코드 입니다 2. saga file 입니다. 여기서 문제가 발생했던 곳이 interceptor 내부에 있는 innerApi.loadMyInfo를 호출 할 때 request Header에 cookie값이 적용이 안되더라구요 config에 withCredential : true로 적용을 했는데도요.. 여기까지가 문제였고 정상적으로 데이터를 받아오는 소스는 아래와 같습니다 위 이미지와 같이 interceptor를 활용 안하고 바로 불러오면 Header에 Cookie가 담겨서 API 콜이 정상적으로 되더라구요.. 의심이라고 할만한곳은 Host에서 차이가 있는 부분밖에 없는데 (interceptor는 포트포워딩을 한 서버의 IP / port가 나오고 아닌경우는 localhost라고 나오더라구요) 이해가 안가는게 interceptor는 withCredentials 설정까지 해줘도 안되고 그냥 호출한 부분은 아무런 코드작성 없이도 정상적으로 된것입니다... 개인적으로 모든 Api 콜을 interceptor를 활용하여 하고싶은데 혹시 방법이 있을까요? 긴 글 읽어주셔서 감사드립니다..
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
시퀄라이즈 여러 쿼리 보내기, Promise async await 개념
안녕하세요. 항상 강의 보면서 도움 많이 받고 있습니다. 프론트에서 체크박스로 제품을 열람가능한 유저를 선택해서 DB에서 기존 데이터를 삭제하고 체크한 유저를 다시 추가해주는 식으로 제품열람가능 유저를 업데이트 하려고 하는데요 제품모델과 유저 모델은 Item.associate = (db) => { // 릴레이션(관계) 정의 db.Item.belongsTo(db.User); // 제품 등록한 유저 db.Item.belongsToMany(db.User, { through: 'ItemUsers', as: 'ItemViewUsers'}); // 제품 노출할 유저 }; .................. User.associate = (db) => { // 릴레이션(관계) 정의 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Providers', foreignKey: 'customerId' }); // 판매자-구매자 관계 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Customers', foreignKey: 'providerId' }); // 판매자-구매자 관계 db.User.belongsToMany(db.Item, { through: 'ItemUsers', as: 'UserViewItems' }); // 열람가능한 제품 }; 이런식입니다. 프론트는 Form과 Checkbox로 단일 ItemId와 UserId 배열을 보내주고 백에서 req.body로 받는 데이터는 { id: '1', values: { customerIds: [ 'tttt', 'ttt3' ] } } 이런식으로 받습니다. // 제품에 열람가능한 고객 등록 router.post('/add-customer', isLoggedIn, async (req, res, next) => { try { console.log('고객등록 req.body',req.body); console.log(req.user.id) const item = await Item.findOne({ where: { id: req.body.id} }); if (!item) { return res.status(404).send('해당 제품이 존재하지 않습니다.'); } if (item.UserId !== req.user.id) { return res.status(404).send('권한이 없습니다.'); } const itemViewUsers = await item.getItemViewUsers(); //제품 열람가능한 유저 전부 제거 if (itemViewUsers) { await Promise.all( itemViewUsers.map( customer => { console.log('\x1b[36m 유저 제거 시도', customer.id); item.removeItemViewUsers(customer.id); }) ); } // 제품 열람가능한 유저 추가 (체크박스로 체크된) if(req.body.values.customerIds){ await Promise.all( req.body.values.customerIds.map( customerId => { console.log('\x1B[31m 유저 추가 시도', customerId); const user = User.findOne({ where: { id: customerId }}) if (!user){ return res.status(404).send('해당 유저가 존재하지 않습니다.'); } item.addItemViewUsers(customerId); }) ); } res.status(200).json(item); } catch (error) { console.error(error); next(error); // status 500 } }); 유저 tttt가 이미 등록된 상태에서 유저 tttt, ttt3 를 선택해서 등록하면 DB에는 ttt3만 등록된 결과가 나옵니다... 실제 INSERT 문도 하나만 실행되네요. 항상 이런건 아니고 될때도 있습니다. 비동기 부분이 꼬여서 그런것같은데 제가 promise 개념이 약해서 어떤식으로 해결할 수 있을지 궁금합니다. Promise, async, await 부분은 제로초님 강의 꼭 다시 보겠습니다. 백엔드 로그는 이러합니다. 고객등록 req.body { id: '1', values: { customerIds: [ 'tttt', 'ttt3' ] } } tester1 Executing (default): SELECT `id`, `codeName`, `name`, `packageName`, `unit`, `msrp`, `supplyPrice`, `imgSrc`, `createdAt`, `updatedAt`, `UserId` FROM `Items` AS `Item` WHERE `Item`.`id` = '1'; Executing (default): SELECT `User`.`id`, `User`.`password`, `User`.`company`, `User`.`name`, `User`.`phone`, `User`.`email`, `User`.`role`, `User`.`createdAt`, `User`.`updatedAt`, `ItemUsers`.`createdAt` AS `ItemUsers.createdAt`, `ItemUsers`.`updatedAt` AS `ItemUsers.updatedAt`, `ItemUsers`.`ItemId` AS `ItemUsers.ItemId`, `ItemUsers`.`UserId` AS `ItemUsers.UserId` FROM `Users` AS `User` INNER JOIN `ItemUsers` AS `ItemUsers` ON `User`.`id` = `ItemUsers`.`UserId` AND `ItemUsers`.`ItemId` = 1; 유저 제거 시도 tttt 유저 추가 시도 tttt 유저 추가 시도 ttt3 Executing (default): DELETE FROM `ItemUsers` WHERE `ItemId` = 1 AND `UserId` IN ('tttt') Executing (default): SELECT `id`, `password`, `company`, `name`, `phone`, `email`, `role`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 'tttt'; Executing (default): SELECT `createdAt`, `updatedAt`, `ItemId`, `UserId` FROM `ItemUsers` AS `ItemUsers` WHERE `ItemUsers`.`ItemId` = 1 AND `ItemUsers`.`UserId` IN ('tttt'); POST /item/add-customer/ 200 9.485 ms - 236 Executing (default): SELECT `id`, `password`, `company`, `name`, `phone`, `email`, `role`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 'ttt3'; Executing (default): SELECT `createdAt`, `updatedAt`, `ItemId`, `UserId` FROM `ItemUsers` AS `ItemUsers` WHERE `ItemUsers`.`ItemId` = 1 AND `ItemUsers`.`UserId` IN ('ttt3'); Executing (default): INSERT INTO `ItemUsers` (`createdAt`,`updatedAt`,`ItemId`,`UserId`) VALUES ('2021-12-22 05:56:16','2021-12-22 05:56:16',1,'ttt3'); Executing (default): SELECT `id`, `password`, `company`, `name`, `phone`, `email`, `role`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 'tester1'; Executing (default): SELECT `User`.`id`, `User`.`company`, `User`.`name`, `User`.`phone`, `User`.`email`, `User`.`role`, `User`.`createdAt`, `User`.`updatedAt`, `Customers`.`id` AS `Customers.id`, `Customers`.`company` AS `Customers.company`, `Customers`.`name` AS `Customers.name`, `Customers->UsersRelation`.`createdAt` AS `Customers.UsersRelation.createdAt`, `Customers->UsersRelation`.`updatedAt` AS `Customers.UsersRelation.updatedAt`, `Customers->UsersRelation`.`customerId` AS `Customers.UsersRelation.customerId`, `Customers->UsersRelation`.`providerId` AS `Customers.UsersRelation.providerId` FROM `Users` AS `User` LEFT OUTER JOIN ( `UsersRelation` AS `Customers->UsersRelation` INNER JOIN `Users` AS `Customers` ON `Customers`.`id` = `Customers->UsersRelation`.`customerId`) ON `User`.`id` = `Customers->UsersRelation`.`providerId` WHERE `User`.`id` = 'tester1'; Executing (default): SELECT `id`, `password`, `company`, `name`, `phone`, `email`, `role`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 'tester1'
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
노드버드를 마치며
현재 노드버드를 마치며 프론트는 리액트 기초 사용법과 노드버드를 만든것이 전부이고 백엔드는 node를 사용하여 노드버드 서버를 구성하고 배포한 것이 전부 입니다. 개인 프로젝트를 시작하기 앞서서 풀스텍으로 할수있을 만큼 최대한 구현을 하려하나 프론트와 백 중에 어떤 포지션에 더 비중을 둬야할지와 개인적으로 어떤 포지션이 맞는지 고민이 많이 됩니다 (공부가 아직 많이 부족하지만..) 프론트는 기술이 빠르게 변화한다는 점이 있다는걸 들었고 사용자와 직접적으로 연결이 되기 때문에 UI/UX는 프론트 개발자의 기본이라고 알고 있습니다, 백엔드는 비지니스 로직에만 집중하며, 어떠한 서비스의 확장성과 안전성에 집중한다고 스치듯 들었던 기억이 납니다. 백엔드는 node로 구성 할 수 있지만, 미래를 생각하면 java를 배워야되는건가 고민이 됩니다. 현재 큰 기업들은 보통 java를 쓰고 있다고 들어서 만약 제가 백엔드를 고려한다면 java를 배워 경력을 쌓아야되는건지 고민이 됩니다. 제로초님의 조언과 의견을 조금 듣고싶습니다
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
글쓴게 안올라가요.. 글등록은됩니다
post form 우선 포스관련에서 코드올립니다 . ㅠㅠ 잘따라하고 글만 안써지는 문제가 생겨서 ㅅ콘솔로그 찍어보니 onChange 는 잘먹히는 상황인데 서서브밋시 포스트 등록은되나 글은 업로드가 되지 않습니다 .. 문제가 무엇인지 모모르겠습니다. import React, { useRef, useCallback, useState, useEffect } from "react"; import { Form, Input, Button } from "antd"; import { useDispatch, useSelector } from "react-redux"; import { addPost } from "../reducer/post"; const PostForm = () => { const { imagePaths, postAdded } = useSelector((state) => state.post); const [text, setText] = useState(""); const dispatch = useDispatch(); const imageInput = useRef(); const onClickImageUpload = useCallback(() => { imageInput.current.click(); }, [imageInput.current]); useEffect(() => { if (postAdded) { setText(""); } }, [postAdded]); const onChangeText = useCallback((e) => { setText(e.target.value); }, []); const onSubmit = useCallback(() => { dispatch(addPost); }, []); return ( <Form style={{ margin: "10px 0 20px" }} encType="multipart/form-data" onFinish={onSubmit} > <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) => { return ( <div key={v} style={{ display: "inline-block" }}> <img alt={v} /> <div> <Button>제거</Button> </div> </div> ); })} </div> </Form> ); }; export default PostForm; post reducer export const initialState = { mainPosts: [ { id: 1, User: { id: 1, nickname: "제로초", }, content: "첫 번째 게시글", 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: [], postAdded: false, }; const ADD_POST = "ADD_POST"; export const addPost = { type: ADD_POST, }; const dummyPost = { id: 2, content: "더미데이터입니다.", User: { id: 1, nickname: "제로초", }, Images: [], Comments: [], }; const reducer = (state = initialState, action) => { switch (action.type) { case ADD_POST: { return { ...state, mainPosts: [dummyPost, ...state.mainPosts], postAdded: true, }; } default: { return { ...state, }; } } }; export default reducer; postcard import { Card, Button, Avatar, Popover, List, Comment } from "antd"; import { Content } from "antd/lib/layout/layout"; import PropTypes from "prop-types"; import React from "react"; import { RetweetOutlined, HeartTwoTone, HeartOutlined, MessageOutlined, EllipsisOutlined, } from "@ant-design/icons"; import { useSelector } from "react-redux"; import PostImages from "./PostImages"; const PostCard = ({ post }) => { const { me } = useSelector((state) => state.user); const id = me?.id; return ( <div> <Card cover={post.Images[0] && <PostImages images={post.Images} />} actions={[ <RetweetOutlined key="retweet" />, <HeartOutlined key="heart" />, <MessageOutlined key="message" />, <Popover key="more" content={ <Button.Group> {id && post.User.id === id ? ( <> <Button>수정</Button> <Button type="danger">삭제</Button> </> ) : ( <Button>신고</Button> )} </Button.Group> } > <EllipsisOutlined /> </Popover>, ]} > <Card.Meta avatar={<Avatar>{post.User.nickname[0]}</Avatar>} title={post.User.nickname} /> {/* <Image /> */} <Content /> </Card> {/* <CommentForm /> */} {/* <Comments /> */} </div> ); }; PostCard.propTypes = { post: PropTypes.shape({ id: PropTypes.number, User: PropTypes.object, content: PropTypes.string, createdAt: PropTypes.object, Comments: PropTypes.arrayOf(PropTypes.any), Images: PropTypes.arrayOf(PropTypes.any), }), }; export default PostCard;
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
next에서 리액트사용시 자동완성 질문있습니다!
import React, { useState} from 'react'; 이부분에서 useState를 추가할떄에는 자동완성을 지원하는데 const LogimForm = () => { const [id, setId] = useState(''); // 이부분에서 useState를 타이핑할경우 자동완성과 import를 자동추가하지않는데 vsc에서의 해결법이있는지 궁금합니다 const [password, setPassword] = useRef(null); return ( <form> ... </form> ); }; export default LogimForm; 비슷한 질문글이 있어 확인을 해보았는데요 답변으로 웹스톰 고유기능이라고 하신답글을 보긴하였는데 vsc에서도 자동완성이되었는데 next내부라는 이유때문에 자동완성이 되지않는지 해결법은 있는지 궁금합니다 감사합니다!
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
babel-eslint 대신..
babel-eslint가 deprecated되고 @babel/eslint-parser로 사용되는 것 같아서 세팅을 해보았습니다만, airbnb가 잡아주지 못하고 있는 거 같은데 어떤 부분이 잘못된 건지 한번 봐주실 수 있나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
es린트 노드버드 폴더만 적용이안되는 문제
기존에 공부했던 개인프로젝트에서는 es린트가 정상적으로 오류가 발생했었는데 오류가 발생코드를 노드버드 프로젝트에 넣거나 린트문서에서 제공하는 오류유발코드를 넣어봐도 es린트 오류가 발생하지않습니다 검색후 프리티어 문제일까도 싶었지만 해결이되지않았습니다 ㅜㅜ 자문구해봅니다 package.json { "name": "react-nodebird-front", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "next", "build": "next build" }, "author": "Kangikchi", "license": "ISC", "dependencies": { "next": "^9.5.5" }, "devDependencies": { "eslint": "^8.5.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.3", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", "prettier": "^2.5.1" } } es린트파일 { "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, "env": { "browser": true, "node": true, "es6": true }, "extends": ["eslint:recommended", "plugin:react/recommended"], "plugins": ["prettier", "import", "react-hooks"], "rules": { "indent": ["error", 2], // 오류유발을 위해 공백칸룰을 추가해보았지만 eslint오류알림이 발생하지않았습니다 "prettier/prettier": "error" } }
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
새로 올리신 리액트쿼리 버전에서 getServerSideProps대신 getStaticProps 쓰는 이유가 궁금합니다.
안녕하세요. 자꾸 귀찮게 해드려서 죄송합니다. 리액트쿼리로 바꾸신거 조금씩 따라하면서 변형하고 있는데 /user/[id] /post/[id] 에서 getServerSideProps대신 getStaticProps 로 바뀐걸 봤습니다. 정적은 빌드시에 데이터 불러오는거라서 데이터 변경가능성이 있는 post/[id]같은 경우는 정적을 쓰면 안될것같은 느낌인데 무슨 장점이나 혹시 바꾸신 이유가 있을까요? 그리고 queryClient.prefetchQuery에 대해서도 궁금한점이 있습니다. 현재 상품을 등록하는 /item/register.tsx를 작성중이고 로그인된 유저에서 정보를 가져와야 해서 서버사이드 랜더링을 적용중에 있습니다. 유저정보를 가져오는 api export function loadMyInfoAPI() { return axios.get('/user').then((response) => response.data); } back에서 라우터 부분 // 로그인 유저 정보 얻기 router.get('/', async (req, res, next) => { // GET /user 로그인 유지 위해 로그인한 유저의 정보 전송 console.log('req.user?', req.user); try { if (req.user) { // 로그인 됐을경우 const userDataWithoutPassword = await User.findOne({ // 프론트로 보낼 유저 정보를 재가공 where: { id: req.user.id }, attributes: { exclude: ['password'], // 비밀번호 제외 }, include: [ { model: User, as: "Customers", attributes: ["id", "company"], exclude: ['UsersRelation'], } ] }) res.status(200).json(userDataWithoutPassword); console.log(JSON.stringify(userDataWithoutPassword)); } else { res.status(200).json(null); } } catch (error) { console.error(error); next(error); } }); 프론트 /item/register.tsx에서 기본 골자는 const Register = () => { ... const { data: myUserInfo } = useQuery<User>('user', loadMyInfoAPI); ... 중략 return ( <AppLayout> <div>{JSON.stringify(myUserInfo)}</div><br /> ... 중략 <div> <List className="demo-loadmore-list" itemLayout="horizontal" dataSource={myUserInfo.Customers} renderItem={(item) => ( <List.Item key={item.id} actions={[ <> <Button type="primary" shape="circle">등록</Button> </> ]} > <List.Item.Meta title={ <> {item.company}<span> | </span><span>담당자: {item.id}</span> </>} /> </List.Item> )} /> </div> 이렇게 로그인된 유저 정보를 출력해주는 부분이 있습니다. myUserInfo.Customers는 me.followers와 흡사한 개념으로 맍들었고요. 서버사이드 랜더링은 export const getServerSideProps = async (context: GetServerSidePropsContext) => { const cookie = context.req ? context.req.headers.cookie : ''; axios.defaults.headers.Cookie = ''; if (context.req && cookie) { axios.defaults.headers.Cookie = cookie; } const response = await loadMyInfoAPI(); if (!response) { return { redirect: { destination: '/', permanent: false, }, }; } return { props: {}, }; }; 단순히 로그인체크만 해서 로그인이 안됐을경우 리다이랙트만 해주는 서버사이드랜더링을 하면 back에서 console.log('req.user?', req.user); 값은 정상적으로 user객체를 출력합니다. 프론트 페이지도 정상적으로 데이터가 나오고요. JSON.stringify(myUserInfo)로 출력한 데이터는 object {9} id : tester1 company : testers name : test phone : 101010 email : 2 role : NOVICE createdAt : 2021-12-17T04:43:25.000Z updatedAt : 2021-12-17T04:43:25.000Z Customers [2] 0 {2} id : ttt3 UsersRelation {4} 1 {2} id : tttt UsersRelation {4} createdAt : 2021-12-17T08:05:07.000Z updatedAt : 2021-12-17T08:05:07.000Z customerId : tttt providerId : tester1 이렇게 나옵니다. 물론 데이터를 받아와서 주입해주는 부분이 없기때문에 /item/register로 주소를 쳐서 들어가면 오류가 납니다. 데이터를 주입시켜주기 위해 쿼리클라이언트를 사용할 경우 export const getStaticProps = async () => { const queryClient = new QueryClient(); await queryClient.prefetchQuery(['user'], () => loadMyInfoAPI()); return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; }; back에서 user는 undefined로 나오게 되고 오류가 발생합니다.. 어떠한 부분때문에 같은loadMyInfoAPI 를 쓰는데도 불구하고 다른 결과가 나오는지 궁금함니다. 프론트 터미널의 오류메시지 TypeError: Cannot read property 'Customers' of undefined at Register (D:\3programming\excuse_moa\front_rq\.next\server\pages\item\register.js:2657:36) at processChild (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:3353:14) at resolve (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:3270:5) at ReactDOMServerRenderer.render (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:3753:22) at ReactDOMServerRenderer.read (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:3690:29) at Object.renderToString (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:4298:27) at renderPage (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\render.js:596:45) at Object.ctx.renderPage (D:\3programming\excuse_moa\front_rq\.next\server\pages\_document.js:1161:30) at Function.getInitialProps (D:\3programming\excuse_moa\front_rq\.next\server\pages\_document.js:621:19) at Function.getInitialProps (D:\3programming\excuse_moa\front_rq\.next\server\pages\_document.js:1169:87) error - Error: "MyDocument.getInitialProps()" should resolve to an object. But found "undefined" instead. at Object.loadGetInitialProps (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\shared\lib\utils.js:75:15) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:93:5) at async renderDocument (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\render.js:609:30) at async Object.renderToHTML (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\render.js:647:28) at async doRender (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\next-server.js:1149:38) at async D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\next-server.js:1241:28 at async D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\response-cache.js:64:36 { page: '/item/register' } back의 터미널 로그 req.user? undefined GET /user 200 1.716 ms - 4
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
시퀄라이즈 다대다관계 질문이 있습니다.
안녕하세요. 강의 듣고 조금씩 변형해가면서 연습하고 있습니다. User와 Follow 모델을 참고해서 User와 UserRealation (판매자-구매자 관계)를 작성했습니다. const User = sequelize.define('User', { // MySQL에는 users 테이블 생성 id: { // 사업자번호 type: DataTypes.STRING(30), allowNull: false, // 필수 unique: true, // 유일한 값 primaryKey: true, }, ... 중략 User.associate = (db) => { db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Providers', foreignKey: 'customerId' }); // 판매자-구매자 관계 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Customers', foreignKey: 'providerId' }); // 판매자-구매자 관계 판매자가 구매자를 등록하는 (팔로워 등록과 유사)간단한 API는 이렇고요, export function addCustomerAPI(data: { providerId: string, customerId: string } ) { return axios.patch('/user/addcustomer', data).then((response) => response.data); } 이걸 실행하면 라우트에서 try { const customer = await User.findOne({ // 아이디 찾기 where: { id: req.body.customerId, } }); const provider = await User.findOne({ // 아이디 찾기 where: { id: req.body.providerId, } }); if (!customer || !provider) { return res.status(403).send('해당 아이디가 존재하지 않습니다.'); } // await provider.addCustomers(req.body.customerId); await customer.addCustomers(req.body.providerId); res.status(200).json({ customerId: req.body.customerId }); // res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3060'); } 이렇게 처리하는 방식입니다. tester1이라는 판매자 아이디로 로그인해서 tttt라는 구매자를 등록하는 과정인데 제 생각으로는 await provider.addCustomers(req.body.customerId); 이렇게 해야 providerId : tester1, customerId: tttt 이렇게 들어갈것 같은데 실제로 sql에서 까보면 반대로 들어가있습니다. 그래서 await customer.addCustomers(req.body.providerId); 이렇게 해야 제가 의도한대로 들어가는데.. customer에 addcustomer로 providerId를 넣는다는게 좀 이해가 안되서 헷갈리네요. (req.body를 까보면 providerId: tester1, customerId: tttt로 잘 받아왔습니다.) 처음에는 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Providers', foreignKey: 'providerId' }); // 판매자-구매자 관계 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Customers', foreignKey: 'customerId' }); // 판매자-구매자 관계 이렇게 했다가 강의에있는 User모델 보고 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Providers', foreignKey: 'customerId' }); // 판매자-구매자 관계 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Customers', foreignKey: 'providerId' }); // 판매자-구매자 관계 이렇게 바꿔놓은건데도 동일하게 작동합니다. SQL도 잘 못하긴 하지만 그냥 SQL INSERT문으로 생각하면 간단한데 시퀄라이즈 모델 개념이 부족해서 너무 헷갈리네요..
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
간단한질문입니다! 수동설치하는 이유를 알수있을까요?
현영님! 2가지 질문 좀 드리겠습니다 :) 1 . npx create-next-app 안하시고 수동설치 하시는 이유는 무엇인가요? 2.왜 저는 수동설치가 안될까요 ㅠ 윈도우 환경에서는 되는데 맥환경에서는 pakage.json 의 "dev" : "next" dev를 자꾸 찾을수 없다고 나와요..
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
이미지 업로드 질문있습니다 (이미지 확장 가능 thumb 생성 X)
강의 따라서 진행하고 aws s3에 aws-upload.zip 파일을 becket name에 복사한 뒤에 Amazon S3 링크 URL도 저장했습니다. 그런데 이미지 업로드 시 위와 같이 여전히 나옵니다. lambda/index.js 소스코드도 문제가 없습니다. const AWS = require('aws-sdk'); const sharp = require('sharp'); const s3 = new AWS.S3(); exports.handler = (event, context, callback) => { const Bucket = event.Records[0].s3.bucket.name; // react-nodebird-s3 const Key = decodeURIComponent(event.Records[0].s3.object.key); // original/12312312_abc.png, decodeURIComponent: 한글 깨짐현상 해결 console.log('Bucket: ', Bucket, 'Key: ', Key); const filename = Key.split('/')[Key.split('/').length - 1]; // 파일이름 추출 const ext = Key.split('.')[Key.split('.').length - 1].toLowerCase(); // 확장자 추출.toLowerCase(), 확장자 대문자를 소문자로 const requiredFormat = ext === 'jpg' ? 'jpeg' : ext; console.log('filename', filename, 'ext', ext); try { const s3Object = await s3.getObject({ Bucket, Key }).promise(); console.log('original', s3Object.Body.length); const resizedImage = await sharp(s3Object.Body) .resize(400, 400, { fit: 'inside' }) .toFormat(requiredFormat) .toBuffer(); await s3 .putObject({ Bucket, Key: `thumb/${filename}`, Body: resizedImage, }) .promise(); console.log('put', resizedImage.length); return callback(null, `thumb/${filename}`); } catch (error) { console.error(error); return callback(error); } }; // ImagesZoom/index.js <img src={`${v.src.replace(/\/thumb\//, '/original/')}`} alt={v.src} /> // routes/post.js router.post('/images', isLoggedIn, upload.array('image'), (req, res, next) => { console.log(req.files); // 업로드가 어떻게 됬는지 정보들이 담겨있음 res.json(req.files.map((v) => v.location.replace(/\/original\//, '/thumb/'))); // original에서 thumb 이미지를 가져옴 // location 자체에 주소가 담겨있음, PostFrom에 image src에 그대로 전달(backURL 필요 X) }); 그리고 S3 bucket에 thumb 파일이 생성이 되지 않았습니다. lambda 함수에서 모니터링을 해보니 실패라고 떠 있습니다. 무엇이 문제인지 파악이 되지 않아 질문을 올립니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
key: 'image'와 router.post('/image) 관련 질문있습니다.
key: 'image'와 routes/post.js에서 router.post('./images', upload.array('image'))가 일치해야 그대로 받을 수가 있다고 하셨는데 PostForm.js에서 onChangeImages 함수에 매개변수로 e를 받아서 e.target.files를 console 찍어보면 FileList가 나옵니다. 여기서 일치한다는게 뭔지 이해가 안되는데... key: image는 어디에 있고 images는 단수에서 복수가 되는건가요? 조금 이해가 안되서 그러는데 간략하게 설명해주실수 있나요? // components/PostForm.js const onClickImageUpload = useCallback(() => { imageInput.current.click(); }, [imageInput.current]); const onChangeImages = useCallback((e) => { console.log('images', e.target.files); // 배열X, 유사배열 const imageFormData = new FormData(); // 멀티파트 형식으로 서버에 보낼수 있다 [].forEach.call(e.target.files, (f) => { imageFormData.append('image', f); // key: 'image', value(값): f }); dispatch({ type: UPLOAD_IMAGES_REQUEST, data: imageFormData, }); }, []); // routes/post.js router.post('/images', isLoggedIn, upload.array('image'), (req, res, next) => { console.log(req.files); // 업로드가 어떻게 됬는지 정보들이 담겨있음 res.json(req.files.map((v) => v.location)); // location 자체에 주소가 담겨있음, PostFrom에 image src에 그대로 전달(backURL 필요 X) }); // POST /post/images, // upload.array(), upload.single(), upload.none()
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
typescript 관련 질문입니다..ㅠㅠ
[제로초 강좌 질문 필독 사항입니다]질문에는 여러분에게 도움이 되는 질문과 도움이 되지 않는 질문이 있습니다.도움이 되는 질문을 하는 방법을 알려드립니다. 안녕하세요 제로초님~!!강의수가 정말 많아서 제대로 흡수하려고 천천히 이해하며 듣다보니 몇달이..지낫네요 ㅎㅎ 제로초님 수업 js를 ts로 변환하면서 수업을 듣고 있습니다. 그러던중.. getServerSideProps 에서 아래와 같이 타입에러가 나고있는데 ㅠㅠ vscode에서 추천하는 타입도 해보고 다른 내용도 검색해보면서 해봤는데 저 오류 해결이 잘 안되네요 혹시 어떤 오류인지 ㅠㅠ 알고계신가 해서 질문 남깁니당..// pages/index.tsx // store/configureStore.tsx
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
리덕스 미들웨어로 비동기 관리 질문
안녕하세요 제로초님 전역상태 관련해서 궁금한점이 생겨 질문드립니다.1. 예를들어 게시글 생성페이지와 게시글수정페이지가 {title: '' content: ''} 와같이 같은 폼양식이면 같은 전역상태를 사용하는 것인가요? 그렇다면 제로초라는 게시물 수정페이지에 들어가면 게시글아이디를 통해 비동기로 서버로부터 게시물 데이터를 받고 전역상태에 값을 셋팅해준다음 화면에 뿌려주고페이지를 벗어난다면 게시글이라는 전역상태를 모두 빈값으로 초기화를 하여 게시글 생성페이지에 진입시 모두 빈값으로 화면에 뿌려주는 방식인건가요?? 헷갈립니다..2. 다른 질문이긴하지만 컴포넌트 파일에서 컴포넌트 바깥에 데이터를 두게된다면(ex: 상태의 초기값이나 상수) 성능에 안좋은건가요??
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
await user.addFollowers(req.user.id)에서 궁금한점이 있습니다.
user.addFollowers(req.user.id)를 했을경우 데이터 베이스를 보니깐 followedId에는req.user.id가 , followingId에는 userId가 들어가 있는걸 확인했는데요. 저희가 데이터베이스 구조를 짤때 as를 Followers 해주고 foreignkey를 FollowingId로 해줬잖아요? 그러면 user.addFollowers(req.user.id)를 했을경우 as: Followers 의 foreignkey가 FollowingId니깐 req.user.id가 FollowingId에 들어가는게 맞는거 아닌가요?? 왜 반대로 됬는지 이해가 안됩니다 ㅠㅠ