inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

묻고 답해요

173만명의 커뮤니티!! 함께 토론해봐요.

상태관리 라이브러리 관련 수업은 없을까요?

해결됨

[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스

요즘 프론트 공고를 보면, 경력/신입을 불문하고 redux, recoil, mobX 등 상태관리 라이브러리가 필수 자격요건에 들어가있는 경우가 많더라구요. 그런데 해당 props 수업을 듣고 생각나서 커리큘럼을 다시 보니까 다른 기술들은 다 강의가 있는데 딱 상태관리 라이브러리 관련 수업 제목만 안보였습니다. 혹시나 뒤의 강의 내용에 포함되어 있는 건지는 아직 다 보진 못했지만 관련 강의가 없다면 상태관리 라이브러리는 포트폴리오에 굳이 적용시키지 않아도 추후 서류를 넣을 때 포트폴리오에 상태관리 라이브러리 적용 경험이 없다면 합불 당락을 결정할 정도로 하자가 되지 않을지 궁금합니다. 아직 이른 걱정일지는 모르겠지만, 나중에 완강하고 따로 상태관리 라이브러리를 공부해서 추후 포트폴리오 완성 시 별도로 공부해서 적용 시켜볼지, 아니면 적용 시키지 않고 그대로 포트폴리오로 써도 상관 없을지 멘토님의 의견이 궁금합니다. 좋은 강의 감사합니다.

  • graphql
  • node.js
  • seo
  • react
  • nodejs
  • next.js
  • Next.js
FRONTEER 댓글 1 좋아요 0 조회수 497

경로별 페이지명은 무조건 index.js 여야 하나요?

해결됨

[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스

문득 수업 듣다가 궁금해진 부분인데요, boards 폴더 안에 list 폴더 만들고 index.js 만들고 detail 폴더 만들고 index.js 만드는게 아니라, 바로 같은 경로에 list.js, detail.js 이런 식으로 만드는건 next.js에서 안되는 방식일까요?

  • node.js
  • react
  • seo
  • graphql
  • nodejs
  • next.js
  • Next.js
FRONTEER 댓글 1 좋아요 0 조회수 1247

개인 블로그 정리

미해결

따라하며 배우는 리액트 A-Z[19버전 반영]

안녕하세요 선생님 혹시 강의를 듣고 정리한 내용을 개인 블로그에 게시해도 될까요??

  • Next.js
  • redux
  • react
  • next.js
  • tdd
  • typescript
kwonhd 댓글 1 좋아요 0 조회수 482

복습 중 질문이 있습니다!

미해결

[리뉴얼] React로 NodeBird SNS 만들기

제로초 쌤 강의 잘들으면서 구현하고 있다가 갑자기 머리가 꼬여서 복습 확인 차 질문 올립니다! (reduxjs/toolkit 사용하고 있어서 제로초쌤 강의에서 saga를 바꿔보면서 하고 있습니다) (wsl 문제는 mysql안에서 명령문 익히면서 진행하고 있습니다) 컴포넌트에서 어떤 액션을 디스패치 해줄때 안에 dispatch안의 콜백함수의 인자가 액션이 백엔드 주소에 데이터를 요청할때 들어가는 data 값의 형태로 들어가서 그게 백엔드에선 req.body로 들어가고(동적라우팅은 주소에 ${data} 로 넣어준 부분이 백엔드의 req.params.(동적라우팅변수)로 들어가는거고) 그 후에 백엔드의 작업이 끝나면 res.json() 안의 인자가 결국 최종적으로 action.payload로 들어오는게 맞나요!? 이 문제는 로그아웃하고 난 뒤에 regenertion 문제가 생겨서 소스코드도 보고 로직에 문제가 있나 싶어서 돌려보고 해당 문제로 저랑 같이 질문하신 분의 답글도 봤는데 한번 안보이니까 너무 안보이더라구요. 한번만 봐주시면 감사하겠습니다 로그아웃시 코드도 사진 따로 첨부하겠습니다(그 외 기능은 전부 잘 돌아갑니다! 로그아웃 하고 난 뒤에만 오류가 나요..) 항상 질 좋은 강의 답변해주셔서 감사합니다! next.js부분도 화이팅하면서 듣겠습니다~

  • express
  • node.js
  • redux
  • react
  • next.js
  • nodejs
  • Next.js
CJW 댓글 1 좋아요 0 조회수 305

Server Error Error: The default export is not a React Component in page: "/02-02-counter-state"

해결됨

[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스

강의 들을때마다 점점 지치네요.. 맥 윈도우 다쓰는데 매번 오류나고 뭐가문제인지 모르겠네요 공부를 하려고 해도 의욕이 떨어져요 Server Error Error: The default export is not a React Component in page: "/02-02-counter-state" This error happened while generating the page. Any console logs will be displayed in the terminal window. Call Stack Object.renderToHTML file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/render.js (234:19) doRender file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/next-server.js (1392:57) <unknown> file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/next-server.js (1487:34) <unknown> file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/response-cache.js (63:42) ResponseCache.get file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/response-cache.js (80:11) DevServer.renderToResponseWithComponents file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/next-server.js (1423:53) DevServer.renderToResponse file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/next-server.js (1559:39) process.processTicksAndRejections node:internal/process/task_queues (95:5) async DevServer.pipe file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/next-server.js (1111:25) async Object.fn file:///Users/jinho/Desktop/codecamp-frontend-jinho/class/node_modules/next/dist/server/next-server.js (912:21)

  • node.js
  • react
  • seo
  • graphql
  • nodejs
  • next.js
  • Next.js
김진호 댓글 1 좋아요 0 조회수 1367

빌드 오류(babel-eslint -> @babel/eslint-parser)

해결됨

만들면서 배우는 프론트엔드 DO IT 코딩 (Next.js, Typescript)

안녕하세요 강사님. 명쾌한 강의 덕분에 많이 배우고 있습니다. 지금까지는 모르는 부분이나 오류를 직접 찾아보고 해결해왔는데 빌드 부분은 도저히 못 찾겠어서 질문글을 작성하였습니다ㅜㅜ... 수강 도중 빌드에 계속 오류가 생겨 질문 남깁니다. (강사님의 초기설정 .eslintrc.js 파일을 사용시 오류) 초반에 강사님의 깃에서 초기설정 버전 파일을 클론해서 시작할 때부터 ts, tsx 파일 맨 첫줄들이 다 빨간줄이 떴었는데 npm run dev는 실행되길래 무시해도 되는 오류인 줄 알고 진행했습니다. ㅜㅜ .. 빌드가 안되는걸 이제서야 알았어요... babel-eslint 가 @babel/eslint-parser로 버전이 바뀌어 오류가 난다 해서 변경시도도 해보았는데 node_modules에서 못찾는다는 오류가 발생해서 지금은 다시 babel-eslint로 커밋한 시점에 돌려놓은 상태입니다. 원인이 너무 궁금한데 이틀째 아침부터 저녁까지 해결방법만 찾고있는데도 해결을 못했습니다ㅜㅜ... 부끄럽지만 해결 방법에 대해 질문 남깁니다. .eslintrc.js 파일에서 parser : '@typescript-eslint/parser'과 parser:'babel-eslint' 를 같이 쓰면 안되는 것 같지만 둘 중 하나만 지워도 또 에러가 떠서 저 상태를 유지하고 있습니다. 깃 주소는 해결되어 지우겠습니다 긴 글 읽어주셔서 감사합니다. > 현재 오류 //.eslintrc.js 파일 module.exports = { parser: '@typescript-eslint/parser', // Specifies the ESLint parser extends: [ 'airbnb', 'plugin:@typescript-eslint/recommended', 'plugin:import/typescript', 'plugin:prettier/recommended', 'babel-eslint', 'prettier', ], plugins: ['babel-eslint', '@typescript-eslint', 'prettier', 'react-hooks'], parserOptions: { ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features sourceType: 'module', // Allows for the use of imports ecmaFeatures: { jsx: true, }, project: './tsconfig.json', }, parser: 'babel-eslint', env: { browser: true, jest: true, es6: true, node: true, }, globals: { cy: true, Cypress: true, }, rules: { 'arrow-parens': [2, 'always'], '@typescript-eslint/no-unused-vars': [ 1, { argsIgnorePattern: 'res|next|stage|^err|on|config|e', }, ], 'arrow-body-style': ['error', 'as-needed', { requireReturnForObjectLiteral: false }], 'no-param-reassign': [ 2, { props: false, }, ], 'no-unused-expressions': [ 1, { allowTaggedTemplates: true, }, ], '@typescript-eslint/prefer-interface': 0, '@typescript-eslint/explicit-function-return-type': 0, '@typescript-eslint/no-use-before-define': 0, '@typescript-eslint/camelcase': 0, '@typescript-eslint/no-var-requires': 0, '@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/no-non-null-assertion': 0, 'no-console': 0, 'spaced-comment': 0, 'no-use-before-define': 0, 'linebreak-style': 0, 'consistent-return': 0, import: 0, 'func-names': 0, 'import/no-extraneous-dependencies': 0, 'import/prefer-default-export': 0, 'import/no-cycle': 0, 'import/extensions': 0, 'import/no-unresolved': 0, 'space-before-function-paren': 0, 'react/jsx-one-expression-per-line': 0, 'react/no-danger': 0, 'react/display-name': 1, 'react/react-in-jsx-scope': 0, 'react/jsx-uses-react': 1, 'react/forbid-prop-types': 0, 'react/no-unescaped-entities': 0, 'react/prop-types': 0, 'react/jsx-filename-extension': [ 1, { extensions: ['.js', '.jsx', '.tsx'], }, ], 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', quotes: [ 2, 'single', { avoidEscape: true, }, ], 'prettier/prettier': 'error', 'jsx-a11y/href-no-hash': 'off', 'jsx-a11y/anchor-is-valid': [ 'warn', { aspects: ['invalidHref'], }, ], }, }; // package.json 파일 { "name": "blahx2", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@chakra-ui/icons": "^2.0.17", "@chakra-ui/react": "^1.8.8", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "axios": "^1.2.3", "chrome-aws-lambda": "^6.0.0", "firebase": "^9.15.0", "firebase-admin": "^11.4.1", "framer-motion": "^6.5.1", "moment": "^2.29.4", "next": "12.0.7", "playwright-core": "^1.30.0", "react": "17.0.2", "react-dom": "17.0.2", "react-query": "^3.39.3", "react-textarea-autosize": "^8.4.0", "styled-components": "^5.3.3" }, "devDependencies": { "@types/node": "^16.11.13", "@types/react": "^17.0.37", "@types/styled-components": "^5.1.18", "@typescript-eslint/eslint-plugin": "^5.8.0", "@typescript-eslint/parser": "^5.8.0", "babel-eslint": "^10.1.0", "babel-plugin-styled-components": "^2.0.2", "eslint": "8.4.1", "eslint-config-airbnb": "^19.0.2", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^16.1.0", "eslint-config-next": "12.0.7", "eslint-config-prettier": "^8.3.0", "eslint-plugin-babel": "^5.3.1", "eslint-plugin-import": "^2.25.3", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", "prettier": "^2.5.1", "typescript": "^4.5.4" } }

  • next.js
  • Next.js
은선 댓글 2 좋아요 0 조회수 1233

yarn generate 에러

해결됨

[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스

Invalid Custom Plugin "typescript" 라면서 에러가 뜹니다 어떻게 해결해야 할까요?

  • node.js
  • Next.js
  • nodejs
  • seo
  • react
  • next.js
  • graphql
cheolung 댓글 1 좋아요 1 조회수 723

커리큘럼 질문

미해결

따라하며 배우는 리액트 A-Z[19버전 반영]

안녕하세요 ! 이번에 리액트 강의를 구매했는데 커리큘럼 문제로 질문드립니다. 리액트 강의를 듣기전에 따라하며 배우는 노드 ,리액트 시리즈 기본강의=>유튜브=>영화사이트 =>챗봇 =>쇼핑몰 =>리액트 A-Z=>레딧사이트 만들기 이순서로 들을까 하는데 괜찮을까요?

  • Next.js
  • redux
  • typescript
  • react
  • next.js
  • tdd
qorwnsdn11 댓글 1 좋아요 0 조회수 252

쿠키파서 기능

미해결

[리뉴얼] React로 NodeBird SNS 만들기

쿠키파서가 없어도 쿠키가 보내지고 데이터를 받을 때 원본이 오는데 쿠키파서 미들웨어가 필수인건가요?

  • redux
  • express
  • node.js
  • nodejs
  • next.js
  • react
  • Next.js
한승보 댓글 1 좋아요 0 조회수 333

마이너스 버튼 관련 질문

해결됨

따라하며 배우는 리액트 A-Z[19버전 반영]

안녕하세요 아래와 같이 코드를 작성할 경우, 0 으로 입력하면 에러 메세지가 보여집니다. 반대로 플러스 버튼과 동일하게 1로 하면 에러 없이 제대로 작동이 됩니다. 왜 0으로 하면 에러가 발생하는 걸까요? test('when the - button is pressed, the counter change to 0', () => { render(<App />); const buttonElement = screen.getByTestId("minus-button"); // click plus button fireEvent.click(buttonElement); // 카운터가 1에서 -1로 되서 0이 된다. const counterElement = screen.getByTestId("counter"); expect(counterElement).toHaveTextContent(0); })

  • react
  • tdd
  • redux
  • next.js
  • typescript
  • Next.js
이은혜 댓글 1 좋아요 0 조회수 318

index.js 오류

미해결

[리뉴얼] React로 NodeBird SNS 만들기

post/user/등 강좌 되돌려 보면서 틀린 곳이 있나 확인했는데, 어디서 부터 고쳐야 될지 모르겠습니다. index.js import React from 'react'; import { useSelector } from 'react-redux'; import PostForm from '../components/PostForm'; import PostCard from '../components/PostCard'; import AppLayout from '../components/AppLayout' const Home = () => { const { isLoggedIn } = useSelector((state) => state.user); const { mainPosts } = useSelector((state) => state.post); return ( <AppLayout> {isLoggedIn && <PostForm/>} {mainPosts.map((post)=><PostCard key={post.id} post={post}/>)} </AppLayout> ); } export default Home;

  • Next.js
  • express
  • node.js
  • nodejs
  • react
  • next.js
  • redux
홍택수 댓글 2 좋아요 0 조회수 357

8:24분경 코드 오류나서 수정하셨는데!

해결됨

[리뉴얼] React로 NodeBird SNS 만들기

8분 20초경 오류나서 코드를 수정하셨는데, case ADD_POST_TO_ME: return { ...state, me: { ...state.me, // 이부분 왜 추가한건가요? Posts: [{ id: action.data }, ...state.me.Posts], }, }; 코드 주석부분 왜 추가하신건가요..? const dummyUser = (data) => ({ ...data, nickname: "wewewe", id: 1, Posts: [{ id: 1 }], Followings: [{ nickname: "we1" }, { nickname: "we2" }, { nickname: "we3" }], Followers: [{ nickname: "we1" }, { nickname: "we2" }, { nickname: "we3" }], }); dummy데이터 구조를 보면 Posts를 제외한 nickname, id, Followings 등 변하지 않는 데이터들의 불변성을 유지하기 위해서 추가한것이 맞는건가요?!

  • nodejs
  • node.js
  • redux
  • express
  • next.js
  • react
  • Next.js
뽀개즈아 댓글 1 좋아요 0 조회수 302

ADD_COMMENT_SUCCESS 새로운 post에서만 작동 안 하는 문제

해결됨

[리뉴얼] React로 NodeBird SNS 만들기

안녕하세요 제로초 님, redux saga immer, faker 까지만 들었구요, 백엔드는 안 만들었고요. faker가 문제가 있다길래, import { faker } from '@faker-js/faker'; 이거를 대신해서 설치해서 했는데, 이거의 문제는 아닌거 같고요. ADD_COMMENT_SUCCESS가 기존의 post에서는 작동을 하는데, 새로운 post에서만 작동 안 하는 문제가 있습니다. 새로운 post.id가 동작을 안 하나 싶어 const post = draft.mainPosts.find((post)=> { console.log(post.id == action.data.postId); return post.id == action.data.postId; }); 를 해봤는데 역시 true가 나옵니다. 아래는 브라우저 결과 사진들 입니다. 로그인 안 했을 때, 로그인 하고 기존의 post에 comment를 달았을 때 -> 정상적으로 동작 함. 로그인 하고 새로운 post을 하나 추가 -> 정상적으로 동작 함. username이 바뀌는 건 faker를 썼고, userId만 그래도 컴포넌트에서 받아서 써서 지금 ADD_POST_TO_ME 동작해서 1에서 2로 바뀌었구요. 그러나 새로운 post에 comment를 달았을 때 -> 콘솔로그가 true가 나옴에도 동작을 하지 않음. 기존의 post에도 comment가 안 달리는 거면 제가 실수를 한게 분명한데, 새로운 post에만 동작을 안 하는게 이상합니다. ADD_COMMENT_REQUEST를 보냈을 때에도, 갑자기 mainPosts가 초기화 되거나 그런 것도 아니었습니다. 만약 그랬다면 콘솔로그가 false가 나왔어야 합니다. 감사합니다!

  • express
  • node.js
  • redux
  • nodejs
  • next.js
  • react
  • Next.js
신영 유 댓글 6 좋아요 0 조회수 505

강의 관련 질문입니다!

해결됨

만들면서 배우는 프론트엔드 DO IT 코딩 (Next.js, Typescript)

members.add.ts 25번째 줄에 return이 없어도 괜찮은건가요? 또 개인적인 생각으로는 api를 호출하는 부분에서 axios를 사용하면 코드가 더 간단해질 것 같은데 fetch가 사용된 이유가 있을까요? 바쁘실텐데 여러 사소한 질문들 드려서 죄송합니다.

  • next.js
  • Next.js
댓글 1 좋아요 0 조회수 406

useState의 set 함수에 대해서 질문이 있습니다.

해결됨

만들면서 배우는 프론트엔드 DO IT 코딩 (Next.js, Typescript)

useState의 set 함수가 비동기로 실행된다고 이해하고 있는데 setLoading(true); setAuthUser({ uid: authState.uid, email: authState.email, photoURL: authState.photoURL, displayName: authState.displayName, }); setLoading(false); 이 부분의 코드가 어떻게 순서대로 실행이 되는건가요? set 함수끼리는 순서가 지켜지나요?

  • next.js
  • Next.js
ssidajkf 댓글 1 좋아요 0 조회수 498

VsCode : Import하지 않은 컴포넌트 경고표시X(설정?)

미해결

[리뉴얼] React로 NodeBird SNS 만들기

현재 버전 정보들입니다 "next": "^11.1.4", "prop-types": "^15.8.1", "eslint": "^8.33.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0" TypeScript와 React를 사용해서 프로젝트 했을 때는 VsCode에서(웹스톰만의 기능은 아니라고 생각합니다.) import하지 않은 컴포넌트에 대한 경고문이 나와서, 맥북 기준 커맨드+.을 하면 Code Action으로 import을 시켜줄 수 있었습니다. 그런데강의를 진행하면서 컴포넌트를 import하지 않은 상황인데도 불구하고, 따로 경고문이 나타나지 않는데, 이게 어떤 설정을 해야하는건지 잘 모르겠습니다. <Menu /> , <Col /> , <UserProfile /> 같은 컴포넌트들입니다.(코드는 이 정도만 첨부하겠습니다.) import PropTypes from "prop-types"; import Link from "next/link"; import { Menu, Input, Row, Col } from "antd"; ... <Row gutter={8}> <Col xs={24} md={6}> {isLoggedIn ? <UserProfile /> : <LoginForm />} </Col> <Col xs={24} md={12}> {children} </Col> <Col xs={24} md={6}> <a href="https://velog.io/" target="_blank" rel="noreferrer noopener">Velog</a> </Col> </Row> </div> ); }; AppLayout.propTypes = { children: PropTypes.node.isRequired, }; export default AppLayout; Import되지 않은 컴포넌트인 <UserProfile />, <LoginForm /> 경고문이 뜨지 않는 사진도 첨부했습니다.

  • react
  • node.js
  • express
  • redux
  • next.js
  • nodejs
  • Next.js
김정훈 댓글 1 좋아요 0 조회수 715

수정 페이지 마운트 직후, 온체인지 핸들러가 인풋 값을 ""으로 인식하여, 인풋 값를 지웠을 경우, 온체인지 핸들러가 값의 변동을 인식하지 못하는 문제가 있습니다.

해결됨

[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스

안녕하세요 강의 잘 보고있습니다. 수정 페이지 마운트 직후, 온체인지 핸들러가 인풋 값을 ""으로 인식하고, 인풋 값를 지웠을 경우, 온체인지 핸들러가 값의 변동을 인식하지 못하는 문제가 있습니다. 즉, 수정 페이지의 각 인풋 디폴트 값들에 데이터가 불러와진 후, 값을 입력하는 것이 아니라 삭제할 경우에 값의 변동을 온체인지 핸들러가 인식하지 못하여 발생합니다. 따라서, 인풋의 온체인지 핸들러를 통해 인풋 값들이 비어있는지 확인하는 유효성 검사에 작은 오류가 있습니다. 수정페이지 마운트 직후 데이터를 불러온 뒤, 값이 인풋에 입력된 후, 온체인지핸들러가 변동을 인식하게 할 수 있을까요? 혹시 비슷한 인풋 값들을 객체에 묶어 저장하는 방식을 지양해야 할까요? import { type ChangeEvent, useState, useRef, useEffect } from 'react'; import { useRouter } from 'next/router'; import BoardWrite_presenter from './BoardWrite_presenter'; import { CREATE_BOARD, UPDATE_BOARD, UPLOAD_FILE } from './BoardWrite_queries'; import { useMutation, useQuery } from '@apollo/client'; import { type IBoardWrite_container_Props, type IUpdatedVariables, } from './BoardWrite_types'; import { type Address } from 'react-daum-postcode/lib/loadPostcode'; import { checkFileValidation } from '../../commons/checkFileValidation'; import { Modal, Spin } from 'antd'; import type { RcFile } from 'antd/es/upload'; import type { UploadFile } from 'antd/es/upload/interface'; import { getBase64 } from '@/src/commons/utils/utils'; import { FETCH_BOARD } from '../detail/BoardDetail_queries'; import { type IQuery } from '@/src/commons/types/generated/types'; import { Loading3QuartersOutlined } from '@ant-design/icons'; export default function BoardWrite_container( props: IBoardWrite_container_Props ) { const router = useRouter(); const boardId = router.query.boardId; if (props.isEditing && !boardId) { return ( <Spin indicator={<Loading3QuartersOutlined spin />} /> ) } const { data } = useQuery<Pick<IQuery, 'fetchBoard'>>(FETCH_BOARD, { variables: { boardId, }, }); const fetchBoard = data?.fetchBoard; if (props.isEditing && !boardId && !fetchBoard) { return ( <Spin indicator={<Loading3QuartersOutlined spin />} /> ) } const [createBoard] = useMutation(CREATE_BOARD); const [updateBoard] = useMutation(UPDATE_BOARD); const [images, setImages] = useState('youtube'); const [isOpen, setIsOpen] = useState(false); const [writerError, setWriterError] = useState(false); const [passwordError, setPasswordError] = useState(false); const [titleError, setTitleError] = useState(false); const [contentsError, setContentsError] = useState(false); const [valid, setValid] = useState(false); interface ICoreInput { [key: string]: string writer: string password: string title: string contents: string } const [coreInput, setCoreInput] = useState<ICoreInput>({ writer: fetchBoard?.writer ?? '', password: '', title: fetchBoard?.title ?? '', contents: fetchBoard?.contents ?? '' }) const [coreInputErorr, setCoreInputErorr] = useState({ writer: false, password: false, title: false, contents: false }) useEffect(() => { if (fetchBoard) { setCoreInput({ ...coreInput, writer: String(fetchBoard.writer), title: String(fetchBoard.title), contents: String(fetchBoard.contents), }) } }, [data]) const onChangeCoreInput = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { setCoreInput({ ...coreInput, [e.currentTarget.id]: e.currentTarget.value }); setCoreInputErorr({ ...coreInputErorr, [e.currentTarget.id]: false }); const AllInputs: string[] = []; for (const prop in coreInput) { if (prop !== e.currentTarget.id) { AllInputs.push(coreInput[prop]) } else { AllInputs.push(e.currentTarget.value) } } console.log("AllInputs : " + AllInputs) if (!AllInputs.includes('' && "undefined")) { setValid(true); } else setValid(false); }; const onChangeImages = (e: ChangeEvent<HTMLInputElement>) => { setImages(e.target.value); }; const [input, setInput] = useState({ zipcode: "", address: "", addressDetail: "", youtubeUrl: "" }) const onChangeInput = (e: ChangeEvent<HTMLInputElement>) => { setInput({ ...input, [e.target.id]: e.target.value }); }; const onSubmit = async (e: { preventDefault: () => void }) => { e.preventDefault(); if (coreInput.writer === '') { setWriterError(true); } if (coreInput.password === '') { setPasswordError(true); } if (coreInput.title === '') { setTitleError(true); } if (coreInput.contents === '') { setContentsError(true); } if (coreInput.writer && coreInput.password && coreInput.title && coreInput.contents) { try { const result = await createBoard({ variables: { createBoardInput: { writer: coreInput.writer, contents: coreInput.contents, password: coreInput.password, title: coreInput.title, youtubeUrl: input.youtubeUrl, images, boardAddress: { zipcode: input.zipcode, address: input.address, addressDetail: input.addressDetail, }, }, }, }); void router.push(`/boards/${result.data.createBoard._id}`); } catch (error) { if (error instanceof Error) alert(error.message); } } }; const onUpdate = async (e: { preventDefault: () => void }) => { e.preventDefault(); const updatedVariables: IUpdatedVariables = { boardId, password: coreInput.password, updateBoardInput: { contents: coreInput.contents, title: coreInput.title, youtubeUrl: input.youtubeUrl, images, boardAddress: { zipcode: input.zipcode, address: input.address, addressDetail: input.addressDetail, }, }, }; if (!coreInput.password) { setPasswordError(true); } if (!coreInput.title) { setTitleError(true); } if (!coreInput.contents) { setContentsError(true); } if (!coreInput.password && !coreInput.title && !coreInput.contents) { try { const result = await updateBoard({ variables: updatedVariables, }); void router.push(`/boards/${result.data.updateBoard._id}`); } catch (error) { if (error instanceof Error) alert(error.message); } } }; const onToggleModal = () => { setIsOpen((prev) => !prev); }; const handleComplete = (data: Address) => { let fullAddress = data.address; let extraAddress = ''; if (data.addressType === 'R') { if (data.bname !== '') { extraAddress += data.bname; } if (data.buildingName !== '') { extraAddress += extraAddress !== '' ? `, ${data.buildingName}` : data.buildingName; } fullAddress += extraAddress !== '' ? ` (${extraAddress})` : ''; } setInput({ ...input, address: fullAddress, zipcode: data.zonecode }); onToggleModal(); }; const [uploadFile] = useMutation(UPLOAD_FILE); const [imgUrl, setImgUrl] = useState("") const fileRef = useRef<HTMLInputElement>(null) const onClickFile = () => { fileRef.current?.click() } const onChangeFile = async (e: ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; const isValid = checkFileValidation(file); if (!isValid) { return } if (isValid) { try { const result = await uploadFile({ variables: { file } }) setImgUrl(result.data?.uploadFile.url) } catch (error) { if (error instanceof Error) { Modal.error({ content: error.message }) } } } } const [previewOpen, setPreviewOpen] = useState(false); const [previewImage, setPreviewImage] = useState(''); const [previewTitle, setPreviewTitle] = useState(''); const [fileList, setFileList] = useState<UploadFile[]>([]); const handleCancel = () => { setPreviewOpen(false); }; const handlePreview = async (file: UploadFile) => { if (!file.url && !file.preview) { file.preview = await getBase64(file.originFileObj as RcFile); } setPreviewImage(file.url ?? (file.preview as string)); setPreviewOpen(true); setPreviewTitle(file.url ? (file.name || file.url.substring(file.url.lastIndexOf('/') + 1)) : ""); }; return ( <BoardWrite_presenter onChangeInput={onChangeInput} onChangeCoreInput={onChangeCoreInput} onChangeImages={onChangeImages} writerError={writerError} passwordError={passwordError} titleError={titleError} contentsError={contentsError} zipcode={input.zipcode} address={input.address} onSubmit={onSubmit} onUpdate={onUpdate} valid={valid} isEditing={props.isEditing} data={data} isOpen={isOpen} handleComplete={handleComplete} onToggleModal={onToggleModal} imgUrl={imgUrl} onClickFile={onClickFile} onChangeFile={onChangeFile} fileRef={fileRef} fileList={fileList} handlePreview={handlePreview} setFileList={setFileList} previewOpen={previewOpen} previewTitle={previewTitle} handleCancel={handleCancel} previewImage={previewImage} /> ); } import { Button, Image, Modal, Upload } from 'antd'; import DaumPostcodeEmbed from 'react-daum-postcode'; import { Main, Title } from '../../../commons/styles/emotion'; import * as S from './BoardWrite_styles'; import { type IBoardWrite_presenter_Props } from './BoardWrite_types'; import type { UploadProps } from 'antd/es/upload'; import { PlusOutlined } from '@ant-design/icons'; export default function BoardWrite_presenter( props: IBoardWrite_presenter_Props ) { const valid = props.valid; const isEditing = props.isEditing; const data = props.data?.fetchBoard; const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { props.setFileList(newFileList); }; const uploadButton = ( <div> <PlusOutlined /> <div style={{ marginTop: 8 }}>Upload</div> </div> ); return ( <Main> <S.Form> <Title>게시물 {isEditing ? '수정' : '등록'}</Title> <div className="writer"> <S.InputWrapper> <label>작성자</label> <input id="writer" onChange={props.onChangeCoreInput} type="text" placeholder="이름을 입력해주세요." defaultValue={props.data?.fetchBoard.writer ?? ""} readOnly={!!props.data?.fetchBoard.writer} /> {props.writerError && ( <p className="alert">이름을 입력해주세요.</p> )} </S.InputWrapper> <S.InputWrapper> <label>비밀번호</label> <input id="password" onChange={props.onChangeCoreInput} autoComplete="off" type="password" placeholder="비밀번호를 입력해주세요." defaultValue={``} /> {props.passwordError && ( <p className="alert">비밀번호를 입력해주세요.</p> )} </S.InputWrapper> </div> <S.InputWrapper> <label>제목</label> <input id='title' onChange={props.onChangeCoreInput} type="text" placeholder="제목을 작성해주세요." defaultValue={data?.title} /> {props.titleError && ( <p className="alert">제목을 작성해주세요.</p> )} </S.InputWrapper> <S.InputWrapper> <label>내용</label> <textarea id='contents' onChange={props.onChangeCoreInput} placeholder="내용을 작성해주세요." defaultValue={props.data?.fetchBoard.contents} /> {props.contentsError && ( <p className="alert">내용을 작성해주세요.</p> )} </S.InputWrapper> <S.InputWrapper> <label>주소</label> <div className="zipcode"> <input id="zipcode" onChange={props.onChangeInput} type="text" // placeholder="00000" readOnly value={ props.address || (props.data?.fetchBoard.boardAddress?.address ?? "") } /> <button onClick={(e) => { e.preventDefault(); props.onToggleModal(); }} > 우편번호 검색 </button> {props.isOpen && ( <Modal title={'우편번호 검색'} open={props.isOpen} onOk={props.onToggleModal} onCancel={props.onToggleModal} > <DaumPostcodeEmbed onComplete={props.handleComplete} ></DaumPostcodeEmbed> </Modal> )} </div> <input id='address' onChange={props.onChangeInput} className="address" type="text" readOnly value={props.address !== "undefined" ? props.address : ""} /> <input id='addressDetail' onChange={props.onChangeInput} type="text" defaultValue={ data?.boardAddress?.addressDetail ? data?.boardAddress?.addressDetail : '' } /> </S.InputWrapper> <S.InputWrapper> <label>유튜브</label> <input id='youtubeUrl' onChange={props.onChangeInput} type="text" placeholder="링크를 복사해주세요." defaultValue={ data?.youtubeUrl ? data?.youtubeUrl : '' } /> </S.InputWrapper> <S.InputWrapper> <label>사진업로드</label> <Button onClick={props.onClickFile}>사진 업로드</Button> <input style={{ display: "none" }} ref={props.fileRef} onChange={props.onChangeFile} type="file" /> </S.InputWrapper> {props.imgUrl && <Image src={`https://storage.googleapis.com/${props.imgUrl}`}></Image>} <S.InputWrapper> <label>사진 첨부</label> <Upload // action="https://www.mocky.io/v2/5cc8019d300000980a055e76" listType="picture-card" fileList={props.fileList} onPreview={props.handlePreview} onChange={handleChange} > {props.fileList.length >= 8 ? null : uploadButton} </Upload> <Modal open={props.previewOpen} title={props.previewTitle} footer={null} onCancel={props.handleCancel}> <img alt={props.previewTitle} style={{ width: '100%' }} src={props.previewImage} /> </Modal> </S.InputWrapper> <S.InputWrapper> <div className="radios"> <span> <input type="radio" id="youtube" name="radios" value="youtube" defaultChecked onChange={props.onChangeImages} /> <label htmlFor="youtube">유튜브</label> </span> <span> <input type="radio" id="image" name="radios" value="image" onChange={props.onChangeImages} /> <label htmlFor="image">사진</label> </span> </div> </S.InputWrapper> <S.SubmitButton onClick={isEditing ? props.onUpdate : props.onSubmit} valid={valid} disabled={!valid} > {isEditing ? '수정하기' : '등록하기'} </S.SubmitButton> </S.Form> </Main> ); }

  • node.js
  • Next.js
  • react
  • nodejs
  • graphql
  • next.js
  • seo
이경석 댓글 1 좋아요 0 조회수 412

아직 못 들었는데 듣기 시작한 날부터 수강기한 시작할 수 없을까요?

해결됨

만들면서 배우는 프론트엔드 DO IT 코딩 (Next.js, Typescript)

개인 사정으로 결제만 해두고 강의를 하나도 듣지 못했는데, 이제 시간이 조금씩 나서 듣기 시작하려고 합니다. 그런데 강의 기한이 2달 조금 안되게 남아서 수강기한을 다시 설정할 수 없을까요?

  • next.js
  • Next.js
fe 댓글 1 좋아요 0 조회수 364

사용하시는 zsh 테마가 뭐예요?

미해결

만들면서 배우는 프론트엔드 DO IT 코딩 (Next.js, Typescript)

강의 화면에서 터미널을 보았습니다. 사용하시는 쉘과 테마가 궁금합니다. 감사합니다.

  • Next.js
유영기 댓글 2 좋아요 0 조회수 487

인기 태그

인프런 TOP Writers

주간 인기글