묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨따라하며 배우는 리액트 A-Z[19버전 반영]
background white 문제
안녕하세요. 선생님.강의에서 보이는 페이지랑 다르게 제껀App 컴포넌트의 div 색깔이 흰색이더라구요.혹시나 싶어 강의 소스 코드를 전부 확인해봐도 검정색으로 App 컴포넌트의div background 컬러를 지정해주는 부분이 없는 것 같은데어떤 차이가 있길래 제껀 흰색 배경이고 선생님껀 검정색 배경인지 알 수 있을까요?소스코드 첨부합니다. https://github.com/edd1e-dev/react-netflix-clone요약) 배경이 흰색인데 검정색 부분으로 바꿔주는 부분을 못찾겠습니다.
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
이미지업로드 graphql
안녕하세요! 이미지 업로드부분 강의 듣고있는데class 폴더에서 진행하고있고 진행하는 서버에는 파일업로드 mutation이 없어요포폴 서버에는 있는데 강의는 그냥 눈으로 보기만하고 포폴만들때 실습을 해야되는건가요?검색기능 강의도 마찬가지인것 같아요,,, 그뒤 강의는 아직 안들어서 모르겠어요,,,const UPLOAD_FILE = gql` mutation uploadFile($file: Upload!) { uploadFile(file: $file) { url } } `; 이부분입니다,,,ㅠ
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
prefetch 디바운싱
강의 끝부분에 퀴즈로 디바운싱 퀴즈가 나와서 코드를 짰긴했는데,, 레퍼런스가 없어서 제가 짠 코드가 맞는지 알고싶습니다..!아래는 강의 후 추가한 코드입니다. const getDebounce = _.debounce(async (boardId) => { await client.query({ query: FETCH_BOARD, variables: { boardId }, }); }, 500); const prefetchBoard = (boardId: string) => async () => { // await getDebounce(boardId); };개발자 도구에서 네트워크 확인결과 0.5초 뒤에 패치를 하는걸로 확인이 되긴 합니다만..제가 올바르게 짠건가요?
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
이미지 업로드 시 data가 보내지지 않습니다..ㅠㅠ
안녕하세요. 강사님! 강의를 들으며 똑같이 작성했고 혹시 모를 오타가 있는지도 확인하면서 강사님 깃허브도 참조해보았는데 원인을 찾지 못하겠어서 문의드립니다. ㅠ ㅠ이미지 업로드를 하면 아래와 같이 오류가 뜨고 redux dev tools 로 확인해 보았을 때UPLOAD_IMAGES_REQUEST 만 처리되고 SUCCESS 로 넘어가지가 않아요.리퀘스트 되어 있는 데이터를 보면 빈 값인 것을 보니 데이터가 넘어가지 않는 것 같은데 도무지 원인을 모르겠습니다. 업로드를 하면 에러가 나면서 서버연결도 끊깁니다. ㅜㅜ제 코드에 뭐가 문제가 있는 건가요..?ㅠㅠ 확인 후 문제를 알려주시면 너무 감사할 것 같습니다..ㅠㅠㅠ[redux] import produce from "immer"; export const initialState = { mainPosts: [], // 이미지 업로드할 때 이미지의 경로들이 저장됨 imagePaths: [], uploadImagesLoading: false, uploadImagesDone: false, uploadImagesError: null, } export const UPLOAD_IMAGES_REQUEST = "UPLOAD_IMAGES_REQUEST"; export const UPLOAD_IMAGES_SUCCESS = "UPLOAD_IMAGES_SUCCESS"; export const UPLOAD_IMAGES_FAILURE = "UPLOAD_IMAGES_FAILURE"; const reducer = (state = initialState, action) => { return produce(state, (draft) => { switch (action.type) { case UPLOAD_IMAGES_REQUEST: draft.uploadImagesLoading = true; draft.uploadImagesDone = false; draft.uploadImagesError = null; break; case UPLOAD_IMAGES_SUCCESS: draft.imagePaths = action.data; draft.uploadImagesLoading = false; draft.uploadImagesDone = true; break; case UPLOAD_IMAGES_FAILURE: draft.uploadImagesLoading = false; draft.uploadImagesError = action.error; break; default: break; } }); }; export default reducer; [saga]import axios from "axios"; import { all, fork, put, takeLatest, throttle, call, } from "redux-saga/effects"; import { UPLOAD_IMAGES_FAILURE, UPLOAD_IMAGES_REQUEST, UPLOAD_IMAGES_SUCCESS, } from "../reducers/post"; function uploadImagesAPI(data) { return axios.post("/post/images", data); } function* uploadImages(action) { try { const result = yield call(uploadImagesAPI, action.data); yield put({ type: UPLOAD_IMAGES_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: UPLOAD_IMAGES_FAILURE, error: err.response.data, }); } } function* watchUploadImages() { yield takeLatest(UPLOAD_IMAGES_REQUEST, uploadImages); } export default function* postSaga() { yield all([ fork(watchUploadImages), ]); }[components] import React, { useCallback, useEffect, useRef } from "react"; import { useSelector, useDispatch } from "react-redux"; import { Form, Input, Button } from "antd"; import { UPLOAD_IMAGES_REQUEST, REMOVE_IMAGE, ADD_POST_REQUEST, } from "../reducers/post"; import useInput from "../hooks/useInput"; const PostForm = () => { const dispatch = useDispatch(); const { imagePaths, addPostDone } = useSelector((state) => state.post); const [text, onChangeText, setText] = useInput(""); const imageInput = useRef(); useEffect(() => { if (addPostDone) { setText(""); } }, [addPostDone]); const onSubmit = 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 ( <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" name="image" multiple hidden ref={imageInput} onChange={onChangeImages} /> <Button onClick={onClickImageUpload}>이미지 업로드</Button> <Button type="primary" style={{ float: "right" }} htmlType="submit"> 짹짹 </Button> </div> <div> {imagePaths?.map((v, i) => ( <div key={v} style={{ display: "inline-block" }}> <img src={`http://localhost:3065/${v}`} alt={v} style={{ width: "200px" }} /> <div> <Button onClick={onRemoveImage(i)}>제거</Button> </div> </div> ))} </div> </Form> ); }; export default PostForm; [back - routes/post.js]const express = require("express"); 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"); }, filename(req, file, done) { // amanda.png const ext = path.extname(file.originalname); // 확장자 추출(.png) const basename = path.basename(file.originalname, ext); // amanda done(null, basename + "_" + new Data().getTiem() + ext); // amanda15653484.png }, }), limits: { fileSize: 20 * 1024 * 1024 }, // 20MB }); router.post( "/images", isLoggedIn, upload.array("image"), // single , array , none async (req, res, next) => { // POST /post/images console.log(req.files); res.json(req.files.map((v) => v.filename)); } ); router.post("/", isLoggedIn, upload.none(), async (req, res) => { // 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)) { // 이미지를 여러 개 올리면 image: [제로초.png, 부기초.png] const images = await Promise.all( req.body.image.map((image) => Image.create({ src: image })) ); await post.addImages(images); } else { // 이미지를 하나만 올리면 image: 제로초.png const image = await Image.create({ src: req.body.image }); await post.addImages(image); } } const fullPost = await Post.findOne({ where: { id: post.id }, include: [ { model: Image, }, { model: Comment, include: [ { model: User, // 댓글 작성자 attributes: ["id", "nickname"], }, ], }, { model: User, // 게시글 작성자 attributes: ["id", "nickname"], }, { model: User, // 좋아요 누른 사람 as: "Likers", attributes: ["id"], }, ], }); res.status(201).json(fullPost); } catch (error) { console.error(error); next(error); } }); module.exports = router;
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
post 에서 작성 후 mainPosts.map 에서 key={post.id} 가 undefined 로 나옵니다.
에러메세지:ADD_POST_SUCCESS 까지 잘 되었고 content 에 id 도 잘 들어가 있는걸로 보입니다. pages/index.jsimport React, { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import AppLayout from "../components/AppLayout"; import PostCard from "../components/PostCard"; import PostForm from "../components/postForm"; import { LOAD_POSTS_REQUEST } from "../reducers/post"; import { LOAD_USER_REQUEST } from "../reducers/user"; const Home = () => { const me = useSelector((state) => state.user.me); const { mainPosts, hasMorePosts, isLoadingPosts } = useSelector( (state) => state.post ); const dispatch = useDispatch(); useEffect(() => { dispatch({ type: LOAD_USER_REQUEST, }); dispatch({ type: LOAD_POSTS_REQUEST, }); }, []); useEffect(() => { function onScroll() { if ( window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300 ) { if (hasMorePosts && !isLoadingPosts) { dispatch({ type: LOAD_POSTS_REQUEST, }); } } } window.addEventListener("scroll", onScroll); return () => { window.removeEventListener("scroll", onScroll); }; }, []); return ( <AppLayout> {me && <PostForm />} {mainPosts.map((post) => ( <PostCard key={post.id} post={post} /> ))} </AppLayout> ); }; export default Home; reducers/post.jsimport produce from "immer"; export const initialState = { mainPosts: [], imagePaths: [], hasMorePosts: true, isAddingPost: false, isAddedPost: false, isAddPostErr: null, isRemovingPost: false, isRemovedPost: false, isRemovePostErr: null, isAddingComment: false, isAddedComment: false, isAddCommentErr: null, isRemovingComment: false, isRemovedComment: false, isRemoveCommentErr: null, isLoadingPosts: false, isLoadedPosts: false, isLoadPostsErr: null, }; export const LOAD_POSTS_REQUEST = "LOAD_POSTS_REQUEST"; export const LOAD_POSTS_SUCCESS = "LOAD_POSTS_SUCCESS"; export const LOAD_POSTS_FAILURE = "LOAD_POSTS_FAILURE"; 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) => ({ type: ADD_POST_REQUEST, data, }); const reducer = (state = initialState, action) => { return produce(state, (draft) => { switch (action.type) { case ADD_POST_REQUEST: draft.isAddingPost = true; draft.isAddedPost = false; draft.isAddPostErr = null; break; case ADD_POST_SUCCESS: draft.isAddingPost = false; draft.mainPosts.unshift(action.data); draft.isAddedPost = true; break; case ADD_POST_FAILURE: draft.isAddingPost = false; draft.isAddedPost = false; draft.isAddPostErr = action.error; break; case REMOVE_POST_REQUEST: draft.isRemovingPost = true; draft.isRemovedPost = false; draft.isRemovePostErr = null; break; case REMOVE_POST_SUCCESS: draft.isRemovingPost = false; draft.mainPosts = state.mainPosts.filter((v) => v.id !== action.data); draft.isRemovedPost = true; draft.isRemovePostErr = null; break; case REMOVE_POST_FAILURE: draft.isRemovingPost = false; draft.isRemovedPost = false; draft.isRemovePostErr = action.error; break; case ADD_COMMENT_REQUEST: draft.isAddingComment = true; draft.isAddedComment = false; draft.isAddCommentErr = null; break; case ADD_COMMENT_SUCCESS: draft.isAddingComment = false; const post = draft.mainPosts.find((v) => v.id === action.data.PostId); post.Comments.unshift(action.data); draft.isAddedComment = true; break; case ADD_COMMENT_FAILURE: draft.isAddingComment = false; draft.isAddedComment = false; draft.isAddCommentErr = action.error; break; case LOAD_POSTS_REQUEST: draft.isLoadingPosts = true; draft.isLoadedPosts = false; draft.isLoadPostsErr = null; break; case LOAD_POSTS_SUCCESS: draft.isLoadingPosts = false; draft.mainPosts = draft.mainPosts.concat(action.data); draft.isLoadedPosts = true; break; case LOAD_POSTS_FAILURE: draft.isLoadingPosts = false; draft.isLoadedPosts = false; draft.isLoadPostsErr = action.error; break; default: break; } }); }; export default reducer;sagas/post.jsimport { delay, all, fork, put, takeLatest, call } from "redux-saga/effects"; import axios from "axios"; import { ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_FAILURE, ADD_COMMENT_FAILURE, ADD_COMMENT_SUCCESS, ADD_COMMENT_REQUEST, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS, REMOVE_POST_FAILURE, LOAD_POSTS_REQUEST, LOAD_POSTS_SUCCESS, LOAD_POSTS_FAILURE, } from "../reducers/post"; import { ADD_POST_TO_ME, REMOVE_POST_FROM_ME } from "../reducers/user"; function addPostAPI(data) { return axios.post( "/post", { content: data }, { withCredentials: true, } ); } function* addPost(action) { try { const result = yield call(addPostAPI, action.data); yield put({ type: ADD_POST_SUCCESS, content: result.data }); yield put({ type: ADD_POST_TO_ME, data: result.data.id, }); } catch (err) { console.log(err); yield put({ type: ADD_POST_FAILURE, error: err.response.data, }); } } function removePostAPI(data) { return axios.delete("/post", data); } function* removePost(action) { try { yield delay(1000); yield put({ type: REMOVE_POST_SUCCESS, data: action.data, }); yield put({ type: REMOVE_POST_FROM_ME, data: action.data, }); } catch (err) { yield put({ type: REMOVE_POST_FAILURE, error: err.response.data, }); } } function addCommentAPI(data) { return axios.post(`/post/${data.postId}/comment`, data); //POST /post/1/comment } function* addComment(action) { try { const result = yield call(addCommentAPI, action.data); yield put({ type: ADD_COMMENT_SUCCESS, data: result.data }); } catch (err) { console.log(err); yield put({ type: ADD_COMMENT_FAILURE, error: err, }); } } function loadPostsAPI() { return axios.get(`/posts`); } function* loadPosts(action) { try { const result = yield call(loadPostsAPI, action.data); yield put({ type: LOAD_POSTS_SUCCESS, data: result.data }); } catch (err) { console.log(err); yield put({ type: LOAD_POSTS_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); } function* watchLoadPosts() { yield takeLatest(LOAD_POSTS_REQUEST, loadPosts); } export default function* postSaga() { yield all([ fork(watchAddPost), fork(watchRemovePost), fork(watchAddComment), fork(watchLoadPosts), ]); }routes/posts.jsconst express = require("express"); const { Post, Image, User, Comment } = require("../models"); const router = express.Router(); router.get("/", async (req, res, next) => { // GET /posts try { const posts = await Post.findAll({ limit: 10, order: [ ["createdAt", "DESC"], [Comment, "createdAt", "DESC"], ], include: [ { model: User, attributes: ["id", "nickname"], }, { model: Image, }, { model: Comment, include: [{ model: User, attributes: ["id", "nickname"] }], }, ], }); res.status(200).json(posts); } catch (err) { console.error(err); next(err); } }); module.exports = router; sql 테이블도 잘 들어가있고, 새로 고침 하면 포스팅은 잘 되어 있습니다.
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
.next파일
로컬에서 Next를 빌드해서 aws 인스턴스에서 Npm start를 하려고 하는데 .next파일을 git에 푸쉬했을때 문제가 생기는 부분이 있을까요? 보안이라던지
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
프론트서버를 s3로 배포하는 방식은 어떤가요?
강좌에서는 ec2 인스턴스 2개로 프론트 백엔드를 유지하는 방식인데 블로그 글들을 찾아보면 프론트서버는 s3로 유지하는 경우가 많더라구요. Get post patch delete 같은 요청에 열려있다는 단점은 있지만 프론트서버같은 정적인 컨텐츠에는 큰 무리가 없고 무엇보다 ec2 2개를 만드는것보다 과금이 없다는건데.. 실제 서비스를 운영할때는 어떤방식으로 하는 편인가요?
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
[section09] Quiz Ant-design DatePicker library
안녕하세요 section09 퀴즈에서 Ant-design Date picker 를 사용하는 과정에서 질문이 생겨 여쭤봅니다. import { DatePicker} from 'antd'; import type { DatePickerProps } from 'antd'; import { useState } from 'react'; export default function AndDesignPage() { const [date, setDate] = useState(''); const [month, setMonth] = useState(''); const onChange: DatePickerProps['onChange'] = (date, dateString) => { console.log(date); setDate(dateString); setMonth(date?.$M + 1);👈👈👈 1️⃣ }; return ( <> <h1>Q2. DatePicker</h1> <span> <DatePicker onChange={onChange} />👈👈👈 2️⃣ </span> {month && ( <> <p>선택날짜 : {date}</p> <p>{month}월을 선택하셨습니다.</p> </> )} </> ); } 전체 코드는 위와 같고 1️⃣,2️⃣번에 빨간 줄이 쳐졌습니다. 1️⃣번은 아래와 같은 메세지가 뜹니다.console.log(date)를 했을 때 아래와 같이 콘솔이 나와서 $M을 활용했는데 작동도 잘 되고 콘솔메세지에도 따로 에러가 뜨지는 않습니다. 2️⃣번은 아래와 같은 문제입니다에러메세지 전문은 다음과 같습니다.(alias) const DatePicker: PickerComponentClass<PickerProps<Dayjs> & { status?: "" | "warning" | "error" | undefined; hashId?: string | undefined; popupClassName?: string | undefined; rootClassName?: string | undefined; }, unknown> & { WeekPicker: import("./generatePicker/interface").PickerComponentClass<Omit<PickerProps<Dayjs> & { status?: "" | "warning" | "error" | undefined; hashId?: string | undefined; popupClassName?: string | undefined; rootClassName?: string | undefined; }, "picker">, unknown>; MonthPicker: import("./generatePicker/interface").PickerComponentClass<Omit<PickerProps<Dayjs> & { status?: "" | "warning" | "error" | undefined; hashId?: string | undefined; popupClassName?: string | undefined; rootClassName?: string | undefined; }, "picker">, unknown>; YearPicker: import("./generatePicker/interface").PickerComponentClass<Omit<PickerProps<Dayjs> & { status?: "" | "warning" | "error" | undefined; hashId?: string | undefined; popupClassName?: string | undefined; rootClassName?: string | undefined; }, "picker">, unknown>; RangePicker: import("./generatePicker/interface").PickerComponentClass<BaseRangePickerProps<Dayjs> & { dropdownClassName?: string | undefined; popupClassName?: string | undefined; }, unknown>; TimePicker: import("./generatePicker/interface").PickerComponentClass<Omit<Omit<import("rc-picker/lib/Picker").PickerTimeProps<Dayjs>, "locale" | "generateConfig" | "hideHeader" | "components" | "hourStep"> & { locale ... import DatePicker 'DatePicker' cannot be used as a JSX component. Its instance type 'Component<PickerProps<Dayjs> & { status?: "" | "warning" | "error" | undefined; hashId?: string | undefined; popupClassName?: string | undefined; rootClassName?: string | undefined; }, unknown, any> & CommonPickerMethods' is not a valid JSX element. The types returned by 'render()' are incompatible between these types. Type 'React.ReactNode' is not assignable to type 'import("/Users/bible/Bible_Highting/codecamp-frontend-bible/class_quiz/node_modules/@types/react-transition-group/node_modules/@types/react/index").ReactNode'. Type '{}' is not assignable to type 'ReactNode'.ts(2786) 1️⃣,2️⃣번 모두 작동에는 이상이 없으나 빨간줄의 원인과 해결방법을 알고싶어 질문드립니다. 감사합니다!
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
aws 포트지정
노드버드 프로젝트를 하나의 인스턴스에서 프론트와 백 서버 둘다 올리기 위해서aws 인스턴스 3000번 포트와 3065번 포트를 열어놓으려고 하는데 사용자지정tcp에서 열어놓으면 되는걸까요?
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
따로 분리한 getAccessToken() 함수 result의 타입
강의를 보고 똑같이 따라했지만 저는 result에 타입이 unknown이라고 뜨네요. 이때문인지는 모르겠지만 로그인 후 넘어간 페이지에서 버튼 누르는 과정에서 오류가 뜹니다.ㅜ아래는 아폴로 세팅입니다. 문제가 무엇일까요?import { ApolloClient, ApolloLink, ApolloProvider, fromPromise, InMemoryCache, } from "@apollo/client"; import { onError } from "@apollo/client/link/error"; import { createUploadLink } from "apollo-upload-client"; import { useEffect } from "react"; import { useRecoilState } from "recoil"; import { getAccessToken } from "../../../commons/libraries/getAccessToken"; import { accessTokenState } from "../../../commons/store"; const GLOBAL_STATE = new InMemoryCache(); interface IApolloSettingProps { children: JSX.Element; } export default function ApolloSetting(props: IApolloSettingProps) { const [accessToken, setAccessToken] = useRecoilState(accessTokenState); // 3. 프리랜더링 무시 - useEffect useEffect(() => { console.log("지금은 브라우저다"); const result = localStorage.getItem("accessToken"); console.log(result); if (result) setAccessToken(result); }, []); // 에러를 캐치하고 캐치한 에러가 토큰만료면 재발급 받은 후, 기존의 쿼리를 포워드해서 다시 날려준다. const errorLink = onError(({ graphQLErrors, operation, forward }) => { // 1-1. 에러를 캐치 if (graphQLErrors) { for (const err of graphQLErrors) { // 1-2. 해당 에러가 토큰만료 에러인지 체크(UNAUTHENTICATED) if (err.extensions.code === "UNAUTHENTICATED") { return fromPromise( // 2-1. refreshToken으로 accessToken을 재발급 getAccessToken().then((newAccessToken) => { // 2-2. 재발급 받은 accessToken 저장하기 setAccessToken(newAccessToken); // 3-1. 재발급 받은 accessToken으로 방금 실패한 쿼리의 정보 수정하기 if (typeof newAccessToken !== "string") return; operation.setContext({ headers: { ...operation.getContext().headers, // 만료된 토큰이 추가되어 있는 상태 Authorization: `Bearer ${newAccessToken}`, // 토큰만 새것으로 바꿔치기 }, }); }) ).flatMap(() => forward(operation)); // 3-2. 방금 수정한 쿼리 재요청하기 } } } }); const uploadLink = createUploadLink({ uri: "https://backendonline.codebootcamp.co.kr/graphql", // https 로 변경(토큰 정보를 쿠키에 담을 수 있게) headers: { Authorization: `Bearer ${accessToken}` }, credentials: "include", // https 변경으로 추가된 조건 }); const client = new ApolloClient({ link: ApolloLink.from([errorLink, uploadLink as unknown as ApolloLink]), // cache: new InMemoryCache(), cache: GLOBAL_STATE, }); // prettier-ignore // 주석으로 prettier-ignore 해주면 한줄로 바뀌는걸 막아준다 return <ApolloProvider client={client}> {props.children} </ApolloProvider>; }혹시 이부분때문일까요??link: ApolloLink.from([errorLink, uploadLink as unknown as ApolloLink])
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
npm run build관련 질문
aws 한개의 인스턴스에 서버와 프론트 서버를 같이 사용하려고 하는데 프리티어에서는 next js의 num run build까지 하게 되면 서버가 터지는 걸로 알고 있습니다 해서 로컬에서 num run build를 해서 깃허브에 푸쉬한 이후 빌드된 파일을 인스턴스에서 Pull 받으려고 하는데 그러면 기존에 gitignore로 인해서 깃허브로 푸쉬가 안되고 있던 node_modules파일이랑 .next파일을 푸쉬해줘야 ec2에서 Pull을 받으면 빌드된게 반영되는건가요?
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
react-query 적용
제로초쌤 혹시 swr 대신에 react-query로 적용한 버젼을 따로 강의해주시거나 그럴 의향있으신가요!? swr은 slack 강좌랑 nodebird 강좌를 통해서 감을 잡아가고 있는데 react-query 는 공식문서를 보면서도 반밖에 잘 이해가 안가서 어떻게 적용하는지 궁금합니다..!
-
해결됨[리뉴얼] React로 NodeBird SNS 만들기
왜 자기 게시글을 리트윗하는 것을 금지해야하는 지 궁금합니다.
왜 자기 게시글을 리트윗하는 것과자기게 시글을 남이 리트윗 한 거를 자기가 또 리트윗 하는 거를 금지해야하는 지 궁금합니다.실제 트위터에서 보면 제 트윗을 제가 또 리트윗 하는 것이 가능하던데 특별한 이유라도 있나요?
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
컴포넌트 재사용하는 부분에서 궁금한게
여기서 isEdit는 변수인건가요..? vscode에서는 속성이라고 뜨는데 뭔지 헷갈려요..
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
로그인 클릭시 로딩
안녕하세요 제로초님 saga와 reducer 연결하는 강의를 듣고 있는데 로그인 버튼 클릭시 로딩만 되고 다음으로 안 넘어가서 질문 드립니다..에러는 안 나고 리덕스 데브툴즈에는 LOG_IN_REQUEST만 뜨는 상태입니다.console창에서 reducer login은 뜨지만 saga logIn은 안 떠서 sagas/user의 function* login() {} 함수가 실행이 안되는 것 같습니다..index.jsuser/reduceruser/sagaconfigureStore.js아래에 해결한 사람들의 방법으로도 해봤는데 여전히 안되어서 질문 남깁니다...!!
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
타입스크립트의 generic 강의 마지막 요약해주시는 부분에서
안녕하세요 멘토님항상 강의 잘 듣고 있습니다.좀 사소한 질문일 수도 있으나, 알고 싶은 부분이라 질문 글을 쓰게 되었습니다.타입스크립트의 generic 강의 1:06:10 쯤 섹션 수업 내용 요약해주실 때 container를 가급적 최소한 사용하라고 말씀하신 부분에서 궁금한게 있습니다.예를 들어보자면, 게시글 작성 컴포넌트 폴더에서만약 기존에는 container-persenter 방식으로 만들어놨다가BoardWrite.container.tsxBoardWrite.presenter.tsxBoardWRite.queries.ts...이렇게 리스트가 있는 상태에서React-hook-form 이나 custom-hook을 사용하여 리팩토링을 하고나서 container의 내용이 거의 확 줄게되면 container 파일의 코드를 presenter랑 합치고 container 파일은 삭제하는게 좋다 라는 말씀이실까요?그렇다면 리팩토링후 container 파일을 삭제하고 presenter에 합쳤다고 가정할 경우BoardWrite.presenter.tsxBoardWRite.queries.ts...이렇게 남게 되는데요 그러면 BoardWrite.presenter.tsx 라는 파일 명을 presenter로 그대로 둬도 상관없는지, 현업에서도 container가 따로 없어도 presenter라는 파일 명으로 보편적으로 놔두는지 궁금합니다.그리고 container와 presenter를 보편적으로 합치는 기준이 둘을 합쳐도 70~80줄 이내일 경우에 합치는 것인지도 궁금합니다. 감사합니다.
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
포트폴리오 중고마켓 유저 포인트에 관한 뮤테이션/쿼리에 관해서
안녕하세요. 수업 잘 듣고 있습니다. 중고마켓 포토폴리오 과정에서 질문이 있습니다. 캐시를 충전하고 받은 rsp 인자로 받은 데이터들을 활용해서 createPointTransactionOfLoading 뮤테이션을 만드는게 맞나요? 받은 데이터들 사용해서 impUid를 넣어봐도 잘 안되네요포인트를 충전하고, 그 데이터들로 어떤 뮤테이션을 보내고, 어떤 쿼리를 받아야하는거죠..?
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
게시글 등록 레퍼런스 코드 질문
레퍼런스 코드 파일 실행시켜봤는데 컴포넌트 정렬이 안맞습니다.무슨 이유인지를 못찾겠습니다..
-
해결됨Next.js 시작하기(feat. 지도 서비스 개발)
nextjs에서 cra처럼 사용 질문입니다
CRA 환경에서는 클라이언트 사이드로 렌더링 되는건데요NEXTJS에서 서버사이드렌더링 없이 CRA처럼 CRS만으로도 구현할수 있나요?이럴 경우에는 getStaticPros, getServerSideRedering 을 사용안하면 무조건 CRS가 되는건가요?seo 적용안하고 CRS로 하고 싶을 경우 강의에서 말씀하신 [_app] 에 next-seo를 추가하지 않고 getStaticPros, getServerSideRedering 없이 그냥 작업하면 모든 페이지가 CRS가 되는거죠 ?그런데 루트에 [_app], [_document] 이 파일은 무조건 실행이 되더라구요이것의 정체는 무엇인지 궁금합니다 CRS인데 NEXT를 사용하려는 이유가 첫번째, 사이트를 만들때 사용자페이지, 관리자페이지 구분을 두고 사용자페이지는 SSR , 관리자페이지는 CSR을 적용하려고 하고 두번째, 코드스프리팅이 지정해줄 필요없이 자동적으로 모두 되어 있어서 NEXTJS를 사용하려고 합니다
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
mysql_secure_installation password 질문이요
... Failed! Error: SET PASSWORD has no significance for user 'root'@'localhost' as the authentication method used doesn't store authentication data in the MySQL server. Please consider using ALTER USER instead if you want to change authentication parameters.구글링도하고 mysql다시깔아서 local password도 다시 설정했는데 자꾸 이 오류가 나오네요.. 혹시 해결 방법이 있을까요?