묻고 답해요
164만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
훈훈한 자바스크립트_eventListener를 활용한 태그 삭제
아래코드를 입력했는데, newLi dblclick했을때 삭제처리가 되지 않습니다. 문제가 뭘까요? const todoInput = document.querySelector("#todo-input"); const createTodo = function () { const todoList = document.querySelector("#todo-list"); //List자체를 받아오기 const newLi = document.createElement("li"); const newSpan = document.createElement("span"); /** * pseudo: (addEventListner) * 할일이 완료가 된지 안된 일인지 알 수가 없음 -> EventListner를 추가 * 버튼태그와 함께 온클릭 이벤트를 생성 */ //02-addEventListner파트: 버튼태그 추가 및 Event속성(onClick 추가) const newBtn = document.createElement("button"); newBtn.addEventListener("click", () => { //클릭을 했을 때 어떻게 할지에 대한 내용을 익명 함수로 지정 newLi.classList.toggle("complete"); //버튼을 눌렀을 때 새로운 클래스를 추가해준다 }); //02-addEventListner: 당연히 삭제도 되어야할텐데... (더블클릭 -> 삭제) newLi.addEventListener("dblclick", () => { newLi.remove(); }); newSpan.textContent = todoInput.value; newLi.append(newBtn); newLi.appendChild(newSpan); todoList.appendChild(newLi); //왜 굳이 span태그를 만들고 span 태그를 List에 추가한 건지는 알 수가 없으나.. 나중에 알려주겠지.. todoInput.value = " "; console.log(newLi); }; //여기까지만 했을때는 기능상 부족한 부분이 상당히 많음 const keyCodeCheck = function () { if (window.event.keyCode === 13 && todoInput.value) { //만약 키보드값에 대해 enter값이 눌리면 -> createTodo(); } };
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
'정의되지 않음'일 수 있는 개체를 호출할 수 없습니다 오류 질문
안녕하세요 과제 중 궁금한 점이 있어 질문 남깁니다 pages - index 파일export default function DetailBoardPage(): JSX.Element { return ( <> <DetailBoard></DetailBoard> <CommentWrite></CommentWrite> <CommentList></CommentList> </> ); }board list presenter 파일export default function CommentListItemUI( props: ICommentListItemUI ): JSX.Element { // 수정 아이콘 클릭 시 수정 화면으로 전환 const [isEdit, setIsEdit] = useState(false); const onClickUpdateIcon = (): void => { setIsEdit(true); return ( <> {!isEdit && ( . . . )} {isEdit && ( <CommentWrite item={props.item} isEdit={true} setIsEdit={setIsEdit} ></CommentWrite> )} </> ); } CommentWrite 컴포넌트를 두 곳에서 사용하고 있습니다1번에서는 아무런 props 인자를 넘겨주지 않고 있고요2번에서는 3가지 props 인자를 넘겨주고 있습니다 types 파일export interface ICommentWrite { item?: IBoardComment; isEdit?: boolean; setIsEdit?: Dispatch<SetStateAction<boolean>>; }이에 맞춰 작성한 interface 입니다setIsEdit state에 ? 기호를 붙이니CommentWrite 파일(바로 아래 파일)의 props.setIsEdit state 사용 구간에서 '정의되지 않음'일 수 있는 개체를 호출할 수 없다는 오류가 뜹니다 export default function CommentWrite(props: ICommentWrite): JSX.Element { const onClickUpdate = async (): Promise<void> => { . . . props.setIsEdit(false); }; const onClickCancel = (): void => { props.setIsEdit(false); }; return ( . . . ); } 찾아보니 undefined / null 값을 제대로 다루지 못할 때 생기는 오류라고 하는데요해당 state는 한 곳에서만 props 인자로 전달하고 있기 때문에 undefined 값이 나오는 경우가 필연적입니다이런 경우 해결할 수 있는 방법이 있을까요? 만약 타입 지정과 관련된 방법으로 해결할 수 없는 문제라면 props 인자를 한 곳에서만 받도록 파일을 합칠까 합니다
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
git hub 연결에 4시간 썼는데 안됩니다ㅜ,,
Github 연결하려 하는데 마지막 단계에서 오류가 자꾸 떠요ㅜㅠ 홈페이지에서 precamp 파일을 지웠다가 다시 만들어도 보고 했는데 계속 저런 알 수 없는 오류가 뜨네요,, 어떻게 다시 연결할 수 있을까요? ㅜㅅㅜ
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
css basic transfrom+transition 레퍼런스 코드 질문있어요
안녕하세요쇼핑몰 레퍼런스 css 코드 104줄에 있는 .item .imgBox img는 왜 들어가있는지 알수있을까요?transition하고 아무 상관 없지 않나요?
-
미해결[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
cypress 를 사용해 modal 에 접근하는 방법을 모르겠습니다
상품등록 페이지를 cypress 를 통해 test 해보던 도중 kakao map 을 사용해 주소입력 하는 부분에서 문제가 발생하고 있습니다. 개발자 도구를 사용해서 kakao map 의 input 의 class 확인해 해당 class name 으로 get()요청을 했지만 인식을 못하고 있습니다
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
TextEncoder is not defined 에러가 발생합니다
FAIL __test__/test/index.test.ts ● Test suite failed to run ReferenceError: TextEncoder is not defined 1 | // 하드코딩된 미니 백엔드 > 2 | import { graphql } from "msw"; | ^다음과 같은 에러가 뜨는 이유가 뭘까요?찾아보니 node 18을 사용하지 않아 발생하는 문제라는데 node -v 을 확인해보면 제대로 18버전을 이용하고 있습니다
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
Next 14 강의
안녕하세요,슬랙 채널에서 Next 14 강의 업로드 예정이시라는 글을 봤습니다. 해당 강좌의 리뉴얼 버전이라고 보면 될까요? 차이점이 궁금합니다.감사합니다.
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
미리보기가 보이지 않습니다
//app.js app.use('/', express.static(path.join(__dirname, 'uploads'))); //PostForm.js import { Button, Form, Input } from 'antd'; import React, { useCallback, useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import useInput from '../hooks/useInput'; import { ADD_POST_REQUEST, REMOVE_IMAGE, UPLOAD_IMAGES_REQUEST, addPost, } from '../reducers/post'; const FormWrapper = styled(Form)` margin: 10px 0 20px; `; const ButtonStyle = styled(Button)` float: 'right'; `; const PostForm = () => { const { imagePaths, addPostLoading, addPostDone } = useSelector( (state) => state.post ); const dispatch = useDispatch(); const imageInput = useRef(); const [text, onChangeText, setText] = useInput(''); useEffect(() => { if (addPostDone) { setText(''); } }, [addPostDone]); const onSubmitForm = useCallback(() => { if (!text || !text.trim()) { return alert('게시글을 작성하세요.'); } const formData = new FormData(); imagePaths.forEach((p) => { formData.append('image', p); }); formData.append('content', text); return dispatch({ type: ADD_POST_REQUEST, data: formData, }); }, [text, imagePaths]); const onClickImageUpload = useCallback(() => { imageInput.current.click(); }, [imageInput.current]); const onChangeImages = useCallback((e) => { console.log('images', e.target.files); //배열모양을 띄는 객체 const imageFormData = new FormData(); [].forEach.call(e.target.files, (f) => { imageFormData.append('image', f); }); dispatch({ type: UPLOAD_IMAGES_REQUEST, data: imageFormData, }); }, []); const onRemoveImage = useCallback( (index) => () => { dispatch({ type: REMOVE_IMAGE, data: index, }); }, [] ); return ( <FormWrapper encType='multipart/form-data' onFinish={onSubmitForm}> <Input.TextArea value={text} onChange={onChangeText} maxLength={140} placeholder='어떤 신기한 일이 있었나요?' /> <div> <input onChange={onChangeImages} type='file' name='image' hidden multiple ref={imageInput} /> <Button onClick={onClickImageUpload}>이미지 업로드</Button> <ButtonStyle type='primary' htmlType='submit'> Twit </ButtonStyle> </div> <div> {imagePaths.map((item, i) => ( <div key={item} style={{ display: 'inline-block' }}> <img src={`http://localhost:3065/${item}`} style={{ width: '200px' }} alt={item} /> <div> <Button onClick={onRemoveImage(i)}>제거</Button> </div> </div> ))} </div> </FormWrapper> ); }; export default PostForm; //post.js const multer = require('multer'); const path = require('path'); const fs = require('fs'); const { Post, Image, Comment, User } = require('../models'); const { isLoggedIn } = require('./middlewares'); const router = express.Router(); try { fs.accessSync('uploads'); } catch (error) { console.log('uploads 폴더가 없으므로 생성합니다.'); fs.mkdirSync('uploads'); } //이미지나 동영상처리는 웬만하면 프론트에서 클라우드로 바로 올리는게 좋다. const upload = multer({ storage: multer.diskStorage({ destination(req, file, done) { done(null, 'uploads'); }, //배포때는 s3로 , 개발에는 드라이브에 filename(req, file, done) { const ext = path.extname(file.originalname); //확장자 추출 const basename = path.basename(file.originalname, ext); done(null, basename + '_' + new Date().getTime() + ext); //이나당151817842.png }, }), limits: { fileSize: 20 * 1024 * 1024 }, //20MB }); router.post('/', isLoggedIn, upload.none(), async (req, res, next) => { // POST /post try { const post = await Post.create({ content: req.body.content, UserId: req.user.id, }); if (req.body.image) { if (Array.isArray(req.body.image)) { const images = await Promise.all( req.body.image.map((image) => Image.create({ src: image })) ); await post.addImages(images); } else { const image = await Image.create({ src: req.body.image }); await post.addImages(image); } } console.log('POST', post); const fullPost = await Post.findOne({ where: 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 (error) { console.error(error); next(error); } }); router.post( '/images', isLoggedIn, upload.array('image'), async (req, res, next) => { //POST /post/images try { console.log(req.files); res.json(req.files.map((v) => v.filename)); } catch (error) { console.error(error); next(error); } } ); 안녕하세요 제로초님! 질문이 있습니다.. 파일은 제대로 올라가서 uploads 폴더안에 있는데 화면에서 이미지를 가져오지 못하고있습니다.. 프론트-백 api통신도 잘 되는데, 왜 개발자도구-네트워크에서 이미지를 가져올 수 없는지 이유를 모르겠습니다 ㅠㅠ
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
await에 대해 약간 헷갈리게되버렸어요
수업에서 async await를 쓰게 되면,await 뒤쪽 함수가 실행 된 후, await를 보자마자 어? 오래걸리겟네? 하면서 스레드가 해당 async함수를 통째로 마이크로큐에 넣어버린다고 말씀하셨는데요.만약 await뒤의 함수가 Promise를 반환하게 되면, 해당 Promise를 반환하는 함수가 먼저 큐에 쌓이고, 그 뒤에 async함수가 들어가서 pipo구조에 따라, promise가 반환된 후 async함수가 실행되면서 기다리는것처럼 보인다고 말해주셨습니다. 근데... 이 말 대로라면, await 함수 뒤에 굳이 Promise가 올 필요가 있나요? XMLHttpRequest()가 오더라도, 콜스택에서 해당요청이 실행된 후, 결과값을 가진 상태에서 마이크로큐로 들어가니까, 똑같이 await가 되는게 아닌지요? 분명 순차적으로 수업을 들으면서 다 이해햇다고 생각햇는데갑자기 헷갈립니다.
-
해결됨Next.js 시작하기(feat. 지도 서비스 개발)
서버사이드 렌더링을 할 때, 서버는 어디에 있는 건가요?
선생님, 안녕하세요. 너무 기초적인 질문일 수 있지만, 갑자기 궁금해져서 질문드립니다. 서버사이드 렌더링의 경우, 서버에서 pre-render된 코드를 만들어서 보내주고, 클라이언트에서 hydrate를 해서 페이지를 만든다고 알고 있습니다. 클라이언트 사이드 렌더링의 경우는 html코드와 js코드를 함께 보내서, 클라이언트에서 페이지를 만들구요. 서버사이드 렌더링을 할 때, pre-render된 코드를 만드는 서버는 각 클라이언트들의 컴퓨팅 자원을 사용하는 건지(클라이언트의 컴퓨터에서 만드는 건지), 아니면 배포하는 중앙(?)서버(aws ec2 또는 이 강의에서 처럼 vercel)의 컴퓨팅 자원을 사용하는 건지(서버에서 만드는 건지) 궁금합니다. 클라이언트 측에서 다 계산을 한다고 하면, 중앙(?)서버의 부담이 적을테지만, 중앙(?)서버에서 한다고 하면, 부담이 갈 수 있을 것 같아서요. 최근 next.js 14버전이 되면서 디비와 직접 통신하는 등 기능이 많이 생겼는데, 이 통신하는 등의 역할은 client side가 아닌 server side에서만 가능하더라구요. 그래서 server side라는 게 중앙서버 하나에만 있다면, 계산이 오래걸린다거나 트래픽이 늘어나거나 한다면 다른 사용자들에게도 영향을 끼치지 않을까 궁금했습니다.(자동 확장이 되지 않을때) 또, 이런식으로 간다면 따로 백엔드 서버가 필요없게 될 수도 있겠다라고도 생각해봤는데, 어떻게 보고계시는지도 궁금합니다.
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
구글 콘솔로그 오류창이 계속 떠요
구글 웹에서 콘솔창을 켜면 계속 이 오류 화면이 뜨는데, 처음에는 무시하고 진행했는데 몇 달간 계속 뜨고 타이머 실습할 때 조금 끊기는 것 같기도 해요ㅜㅠ GPT에 물어보니까 권한이 적절치 않다고 하는데 어떻게 해결할 수 있는 건가요??ㅜㅜ
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
[공유] Next.js 12버전 redirect
import { useRouter } from 'next/router'; const Profile = () => { ... const router = useRouter(); if (!me) { router.push('/'); return null; } ... }이런식으로 하면 잘 동작해요~
-
미해결따라하며 배우는 리액트 A-Z[19버전 반영]
체크박스 클릭 구현 함수에 대해서 질문드립니다.
input checkbox 에서onChange={() => this.handleCompleChange(data.id)}함수를 실행하게끔하는데 () => 는 왜들어가는걸까요? 예를들어 같은 방식의 함수중에 onChange={this.handleChange} 함수의경우엔 () => 가 없더라구요. 구현 부분은 똑같이 함수이름 = (파라미터) => {} 로 구현되어 있습니다.
-
미해결따라하며 배우는 리액트 A-Z[19버전 반영]
영화 나열을 위한 Row 컴포넌트 생성하기
8분 47초 까지 따라했는데, 오류가 뜹니다.import React, { useEffect } from 'react' import axios from '../api/axios' export default function Row({ isLargeLow, title, id, fetchUrl }) { useEffect (() => { fetchMovieData(); }, []); const fetchMovieData = async () => { const request = await axios.get(fetchUrl); console.log('request', request); }; return ( <div> </div> ) } 터미널에서는 아래와 같은 오류가 뜨고,WARNING in ./src/components/Banner.css (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[5].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[5].use[2]!./node_modules/source-map-loader/dist/cjs.js!./src/components/Banner.css) Module Warning (from ./node_modules/postcss-loader/dist/cjs.js): Warning (75:5) autoprefixer: start value has mixed support, consider using flex-start instead WARNING in [eslint] src/components/Row.js Line 7:8: React Hook useEffect has a missing dependency: 'fetchMovieData'. Either include it or remove the dependency array react-hooks/exhaustive-deps webpack compiled with 2 warnings 아래는 크롬에서 뜨는 오류입니다.Uncaught runtime errors: × ERROR Cannot read properties of undefined (reading 'fetchNetflixOriginals') TypeError: Cannot read properties of undefined (reading 'fetchNetflixOriginals') at App (http://localhost:3000/static/js/bundle.js:46:61) at renderWithHooks (http://localhost:3000/static/js/bundle.js:20698:22) at mountIndeterminateComponent (http://localhost:3000/static/js/bundle.js:23984:17) at beginWork (http://localhost:3000/static/js/bundle.js:25280:20) at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:10290:18) at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:10334:20) at invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:10391:35) at beginWork$1 (http://localhost:3000/static/js/bundle.js:30265:11) at performUnitOfWork (http://localhost:3000/static/js/bundle.js:29512:16) at workLoopSync (http://localhost:3000/static/js/bundle.js:29435:9) ERROR Cannot read properties of undefined (reading 'fetchNetflixOriginals') TypeError: Cannot read properties of undefined (reading 'fetchNetflixOriginals') at App (http://localhost:3000/static/js/bundle.js:46:61) at renderWithHooks (http://localhost:3000/static/js/bundle.js:20698:22) at mountIndeterminateComponent (http://localhost:3000/static/js/bundle.js:23984:17) at beginWork (http://localhost:3000/static/js/bundle.js:25280:20) at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:10290:18) at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:10334:20) at invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:10391:35) at beginWork$1 (http://localhost:3000/static/js/bundle.js:30265:11) at performUnitOfWork (http://localhost:3000/static/js/bundle.js:29512:16) at workLoopSync (http://localhost:3000/static/js/bundle.js:29435:9) ERROR Cannot read properties of undefined (reading 'fetchNetflixOriginals') TypeError: Cannot read properties of undefined (reading 'fetchNetflixOriginals') at App (http://localhost:3000/static/js/bundle.js:46:61) at renderWithHooks (http://localhost:3000/static/js/bundle.js:20698:22) at mountIndeterminateComponent (http://localhost:3000/static/js/bundle.js:23984:17) at beginWork (http://localhost:3000/static/js/bundle.js:25280:20) at beginWork$1 (http://localhost:3000/static/js/bundle.js:30243:18) at performUnitOfWork (http://localhost:3000/static/js/bundle.js:29512:16) at workLoopSync (http://localhost:3000/static/js/bundle.js:29435:9) at renderRootSync (http://localhost:3000/static/js/bundle.js:29408:11) at recoverFromConcurrentError (http://localhost:3000/static/js/bundle.js:28900:24) at performConcurrentWorkOnRoot (http://localhost:3000/static/js/bundle.js:28812:26)
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
섹션 13에 타입스크립트 실행관련
타입스크립트 본수업은 문제없이 진행했는데 freeboard 부분을 진행하다가 api 관련 타입스크립트 설치 과정을 하는데 에러가 보입니다.. 이부분 혹시 뭐때문에 발생하는건지 확인 해주실 수 있으실까요ㅠㅠ
-
미해결따라하며 배우는 리액트 A-Z[19버전 반영]
모달 창 외부 클릭 시 모달 닫게 만드는 Custom Hooks 생성 이 강의 오타부분
모달 창 외부 클릭 시 모달 닫게 만드는 Custom Hooks 생성 이 강의에서 useOnClickOutside custom hooks부분에서 event listener해제하는 부분이 addEventListener로 잘못되어 있내요
-
해결됨손에 익는 Next.js - 공식 문서 훑어보기
비동기 호출 관련 질문있습니다!
혹시 서버 컴포넌트에서만 비동기 호출이 가능한건가요??그렇다면 서버 컴포넌트가 컴포넌트를 서버 내에서 미리 만들어 둘 때, 비동기 호출을 통해 데이터를 가져와서 이 값을 미리 가져와서 컴포넌트를 만드는 건가요??그리고 만약 그렇다면 왜 서버 컴포넌트에서만 비동기 호출이 가능하도록 만드신 건지 의도를 아시고 계신지 궁금합니다! (단순히 서버 컴포넌트의 장점 때문인건가요??)그리고 강의의 진행은 SSR을 사용한다는 가정하에 진행해주셨던 게 맞나요?? (SSG도 동일한 건가요?)
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
카카오맵 이 출력이 안되요
window.kakao에 underfined로 나와요...
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
retweet 관련 질문이 있습니다.
다름이 아니라 리트윗빼고는 문제가 없습니다.하지만 리트윗을 하게 되면 데이터베이스에 userid가 null로 들어가면서 게시글을 불러올때 userid가 없기때문에 오류가 나는 것으로 보입니다... 해결해보려고 노력하는 중입니다만 어디가 문제인지 잘 모르겠습니다.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"); } const upload = multer({ storage: multer.diskStorage({ //어디에 저장할지 diskStorage => 하드디스크에 저장 destination(req, file, done) { done(null, "uploads"); }, filename(req, file, done) { // 제로초.png const ext = path.extname(file.originalname); // 확장자 추출(.png) const basename = path.basename(file.originalname, ext); // 제로초 done(null, basename + "_" + new Date().getTime() + ext); // 제로초15184712891.png }, }), limits: { fileSize: 20 * 1024 * 1024 }, // 20MB }); router.post("/", isLoggedIn, upload.none(), async (req, res, next) => { // 보기에는 "/"로 되어있지만 실제로는 "/post"로 되어있다. try { const hashtags = req.body.content.match(/#[^\s#]+/g); //hashtag 정규식 const post = await Post.create({ content: req.body.content, UserId: req.user.id, }); if (hashtags) { const result = await Promise.all( hashtags.map( (tag) => Hashtag.findOrCreate({ //있으면 가져오고 없으면 추가해라 where: { name: tag.slice(1).toLowerCase() }, }) //[[노드, true], [리액트, true]] 이런식으로 나옴 //slice는 글자만 떼기 위해 ex ) #react인 경우 react만 꺼냄 ) ); await post.addHashtags(result.map((v) => v[0])); //위에 같은 형식이기 때문에 0번째만 꺼내야함 } if (req.body.image) { if (Array.isArray(req.body.image)) { //여러개의 이미지를 올린 경우 const images = await Promise.all( req.body.image.map((image) => Image.create({ src: image })) ); await post.addImages(images); } else { 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 (error) { console.error(error); next(error); } }); router.post("/:postId/comment", isLoggedIn, async (req, res, next) => { //:postId는 동적으로 바뀐다. //POST /post/comment // 보기에는 "/"로 되어있지만 실제로는 "/post"로 되어있다. try { const post = await Post.findOne({ //이 게시물이 진짜 있는지. where: { id: req.params.postId }, }); if (!post) { return res.status(403).send("존재하지 않는 게시글입니다."); } const comment = await Comment.create({ content: req.body.content, PostId: parseInt(req.params.postId, 10), //문자열로 넘어가기 때문에 int형으로 바꿔줘야한다. UserId: req.user.id, }); const fullComment = await Comment.findOne({ where: { id: comment.id }, include: [ { model: User, attributes: ["id", "nickname"], }, ], }); res.status(201).json(fullComment); } catch (error) { console.error(error); next(error); } }); router.patch("/:postId/like", isLoggedIn, async (req, res, next) => { //PATCH /post/1/like\ try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send("게시글이 존재하지 않습니다."); } await post.addLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id }); } catch (error) { console.error(error); next(error); } }); router.delete("/:postId/like", isLoggedIn, async (req, res, next) => { //DELETE /post/1/like try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send("게시글이 존재하지 않습니다."); } await post.removeLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id }); } catch (error) { console.error(error); next(error); } }); router.delete("/:postId", isLoggedIn, async (req, res, next) => { // DELETE /post/10 try { await Post.destroy({ where: { id: req.params.postId, UserId: req.user.id, }, }); res.status(200).json({ PostId: parseInt(req.params.postId, 10) }); } catch (error) { console.error(error); next(error); } }); router.post( "/images", isLoggedIn, upload.array("image"), //PostForm.js에서input에 올린 이미지가 배열로 들어감 (이미지를 여러장 올릴 수 있게 하기 위해서) async (req, res, next) => { // POST /post/images/ //이곳은 이미지 업로드 후 실행되는 부분, 업로드는 위에 upload에서 이미 다 올라감 console.log(req.files); res.json(req.files.map((v) => v.filename)); //프론트로 보내줌 } ); router.post("/:postId/retweet", isLoggedIn, async (req, res, next) => { // POST /post/1/retweet try { const post = await Post.findOne({ where: { id: req.params.postId }, include: [ { model: Post, as: "Retweet", }, ], }); if (!post) { return res.status(403).send("존재하지 않는 게시글입니다."); } if ( req.user.id === post.UserId || (post.Retweet && post.Retweet.UserId === req.user.id) ) { return res.status(403).send("자신의 글은 리트윗할 수 없습니다."); } const retweetTargetId = post.RetweetId || post.id; const exPost = await Post.findOne({ where: { UserId: req.user.id, RetweetId: retweetTargetId, }, }); if (exPost) { return res.status(403).send("이미 리트윗했습니다."); } const retweet = await Post.create({ UserId: req.user.id, RetweetId: retweetTargetId, content: "retweet", }); const retweetWithPrevPost = await Post.findOne({ where: { id: retweet.id }, include: [ { model: Post, as: "Retweet", include: [ { model: User, attributes: ["id", "nickname"], }, { model: Image, }, ], }, { model: User, attributes: ["id", "nickname"], }, { model: Image, }, { model: Comment, include: [ { model: User, attributes: ["id", "nickname"], }, ], }, ], }); res.status(201).json(retweetWithPrevPost); } catch (error) { console.error(error); next(error); } }); module.exports = router; //node에서는 import와 export defau lt를 사용하지 않고 require를 사용한다. //리트윗 case RETWEET_REQUEST: draft.retweetDone = false; draft.retweetLoading = true; draft.retweetError = null; break; case RETWEET_SUCCESS: draft.retweetLoading = false; draft.retweetDone = true; draft.mainPosts.unshift(action.data); break; case RETWEET_FAILURE: draft.retweetLoading = false; draft.retweetError = action.error; break; default: break; } }); export default reducer;//리트윗 function retweetAPI(data) { return axios.post(`/post/${data}/retweet`); //data는 formdata다 } function* retweet(action) { try { const result = yield call(retweetAPI, action.data); yield put({ type: RETWEET_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: RETWEET_FAILURE, error: err.response.data }); } }
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
[공유] input 속성에 hidden 이 적용 안될 때
<input type='file' multiple style={{ display: 'none' }} />display: 'none'으로 줘보세요!