39,600원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
value,setValue 에러
ts가 변수와 함수는 타입을 유추할 수 있는 줄 알았는데 밑 에러가 나는 이유는 타입을 정의해주지 않아서 그런가요,,? 똑같이 any도 써보고 <T>도 써보았는데 해결이 되지 않습니다. 어떻게 해야하나요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
만약 create react-app으로 프로젝트를 작업할 때에는
proxy 설정등의 webpack 설정은 어떤 파일에서 해야 하나요? 현재 강의와 같이 webpack.config.ts 파일을 생성하여서 설정하면 될까요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
component에서 에러
component에러가 해결이 안됩니다.. 그래서 제가 임의로 routes-route로 바꿨더니const App = () => { return ( <Routes> <Route path="/" element={<Navigate to="/login" replace />} /> <Route path="/login" element={<LogIn />} /> <Route path="/signup" element={<SignUp />} /> </Routes> ); }; export default App;이러면 이런 에러가 뜹니다ㅜㅜ파일 구조가 이러한데로그인 창은 이런식으로 구성되어있습니다... 어떻게 해야하나요ㅜ
- 미해결Slack 클론 코딩[실시간 채팅 with React]
index.html 실행
현재 npm run build까지 성공한 상태인데요, index파일을 실행시킨다는 뜻이 터미널에 cd alecture에 npm run start를 하라는 말씀이 맞나요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
checker.emitFiles is not a function
npm run build를 했더니 밑과 같은 에러가 뜹니다.. 어떻게 해야하나요 ㅜ [webpack-cli] Failed to load 'C:\Users\JW\slack_front\alecture\webpack.config.ts' config[webpack-cli] TypeError: checker.emitFiles is not a function at compile (C:\Users\JW\slack_front\alecture\node_modules\typescript-register\index.js:132:26) at Object.req (C:\Users\JW\slack_front\alecture\node_modules\typescript-register\index.js:29:9) at Module.load (node:internal/modules/cjs/loader:1004:32) at Function.Module._load (node:internal/modules/cjs/loader:839:12) at Module.require (node:internal/modules/cjs/loader:1028:19) at require (node:internal/modules/cjs/helpers:102:18) at WebpackCLI.tryRequireThenImport (C:\Users\JW\slack_front\alecture\node_modules\webpack-cli\lib\webpack-cli.js:204:22) at loadConfigByPath (C:\Users\JW\slack_front\alecture\node_modules\webpack-cli\lib\webpack-cli.js:1404:38) at WebpackCLI.loadConfig (C:\Users\JW\slack_front\alecture\node_modules\webpack-cli\lib\webpack-cli.js:1510:44) at WebpackCLI.createCompiler (C:\Users\JW\slack_front\alecture\node_modules\webpack-cli\lib\webpack-cli.js:1785:33)
- 미해결Slack 클론 코딩[실시간 채팅 with React]
백 서버 구축 실패 ( 패키지 설치 에러)
npm i로 패키지를 설치하려하니 이런 에러가 뜹니다. 어떤 방식으로 해결할 수 있을까요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
router path 특수문자 사용 관련 질문 드립니다.
안녕하세요 제로초님 좋은 강의 감사드립니다.제가 react router에 대해 여러 실험을 좀 하다가 url path에 특수문자(ampersand(&), exclamation(!))가 들어갈 수 있다는 사실을 발견했습니다.그런데 url query에서의 특수문자는 특정의미를 갖는 경우가 많은데요. 그러다 보니 path에 과연 특수 문자를 써도 되는가에 대해 의문이 생겼습니다. RFC 공식문서에서는 path에 특수문자를 써도 된다고 하는데 구글의 몇몇 stackoverflow의 글에서는 안된다는 내용이 있어서 혼동이 옵니다 ㅜ혹시 제로초님의 의견은 어떠실지 여쭤봅니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
package.json 관련해서 문의드립니다
안녕하세요 제로초님@types 라는 별칭으로 타입스크립트 추론이 가능한 보조 라이브러리들은devDependencies 에 추가가 되는것이 아닌걸까요??? 일부 라이브러리들은 dependencies 에 추가되어있어 문의드리게 되었습니다! 감사합니다 :)
- 미해결Slack 클론 코딩[실시간 채팅 with React]
개인 프로젝트에서 쿠키가 안들어오는데 여쭤봐도 될까요..?
안녕하세요 제로초님 강의 듣고 친구들과 프로젝트를 하다가 로그인 시 쿠기가 생성되지 않아서 여쭤봅니다..! 프론트와 백 도메인이 달라서 백엔드에서 same-site 설정을 해주고, 프론트 axios에서 withCredentials: true를 설정해줬습니다.네트워크 탭을 보면 쿠키가 전달은 됩니다. 하지만 정작 브라우저의 쿠키는 비어있습니다. 이 오류는 어떻게 해결해야 할까요..?-프론트 코드const headers = { "X-Requested-With": "XMLHttpRequest", }; const onSubmit = useCallback( (e) => { e.preventDefault(); setLogInError(false); axios .post( "https://waycabvav.shop/login", { loginId: id, password: password, }, { withCredentials: true, headers } ) .then((response) => { alert("성공"); }) .catch((error) => { alert("에러"); setLogInError(error.response?.data?.statuseCode === 401); }); }, [id, password] );프론트에서 코드를 이렇게 했습니다..! 로그인 요청은 성공하는데 쿠키가 들어와 있지 않습니다ㅜㅜㅜ 답변해주시면 정말 감사하겠습니다..!
- 미해결Slack 클론 코딩[실시간 채팅 with React]
로그인 부분 swr관련 질문있습니다.
먼저 로그인시 mutate()로 data를 넣어주고, 콘솔에 찍어 data가 잘 들어가는 것을 확인했습니다./workspace/channel로 이동 후, 로그아웃 기능이 제대로 동작하지 않아서 일단 data를 제대로 가져오는지 확인하기 위해 data를 콘솔에 다시 찍어봤는데위 결과처럼 처음엔 제대로 data가 찍혔지만 바로 이어서 값이 날라가버립니다. (이 때문에 로그아웃 버튼을 누르지 않아도 바로 로그인 화면으로 다시 튕겨버립니다.)fetcher 함수도 같이 올립니다. 혹시 잘못 작성하거나 바뀐 부분이 있을까요??너무 좋은 강의 잘 듣고 있습니다!! 감사합니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
webpack.config.ts 질문
http://localhost:3090/오류가 있어서 다른사람이 질문한것에 대한 답변대로 따라하는중에 해결방법을 모르겠어서 질문드립니다. webpack.config.ts에서 devServer: { historyApiFallback: true, port: 3090, devMiddleware: { publicPath: '/dist/' }, static: { directory: path.resolve(__dirname) }, proxy: { '/api/': { target: 'http://localhost:3095', changeOrigin: true, ws: true, }, }, },부분에서 devMiddleware: { publicPath: '/dist/' } 부분에 빨간줄이 그어져서 '{ historyApiFallback: true; port: number; devMiddleware: { publicPath: string; }; static: { directory: string; }; proxy: { '/api/': { target: string; changeOrigin: true; ws: true; }; }; }' 형식은 'Configuration' 형식에 할당할 수 없습니다. 개체 리터럴은 알려진 속성만 지정할 수 있으며 'Configuration' 형식에 'devMiddleware'이(가) 없습니다.이런 문제창이 뜨는데요 어떻게 해결해야하나요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
npx webpack 오류
npx webpack 명령어를 쳤더니 $ npx webpack[webpack-cli] Failed to load 'C:\Users\ws\Desktop\sleact\alecture\webpack.config.ts' config[webpack-cli] Error: Debug Failure. False expression: Non-string value passed to ts.resolveTypeReferenceDirective, likely by a wrapping package working with an outdated resolveTypeReferenceDirectives signature. This is probably not a problem in TS itself. at Object.resolveTypeReferenceDirective (C:\Users\ws\Desktop\sleact\alecture\node_modules\typescript\lib\typescript.js:43192:18) at C:\Users\ws\Desktop\sleact\alecture\node_modules\ts-node\src\index.ts:623:55 at Array.map (<anonymous>) at Object.resolveTypeReferenceDirectives (C:\Users\ws\Desktop\sleact\alecture\node_modules\ts-node\src\index.ts:622:33) at actualResolveTypeReferenceDirectiveNamesWorker (C:\Users\ws\Desktop\sleact\alecture\node_modules\typescript\lib\typescript.js:118205:163) at resolveTypeReferenceDirectiveNamesWorker (C:\Users\ws\Desktop\sleact\alecture\node_modules\typescript\lib\typescript.js:118505:26) at processTypeReferenceDirectives (C:\Users\ws\Desktop\sleact\alecture\node_modules\typescript\lib\typescript.js:120002:31) at findSourceFileWorker (C:\Users\ws\Desktop\sleact\alecture\node_modules\typescript\lib\typescript.js:119887:21) at findSourceFile (C:\Users\ws\Desktop\sleact\alecture\node_modules\typescript\lib\typescript.js:119739:26) at C:\Users\ws\Desktop\sleact\alecture\node_modules\typescript\lib\typescript.js:119688:85 이런 오류가 나오는데요.. 오류 해결법을 몰라서 깃허브에 있는 코드를 복사 붙여넣기 했는데도 오류가 뜹니다. ㅠㅠ
- 미해결Slack 클론 코딩[실시간 채팅 with React]
npx sequelize db:create 오류
ws@DESKTOP-9H6S8B6 MINGW64 ~/Desktop/sleact/back$ npx sequelize db:createSequelize CLI [Node: 16.15.0, CLI: 6.4.1, ORM: 6.21.4]Loaded configuration file "config\config.js".Using environment "development".ERROR: Access denied for user 'root'@'localhost' (using password: NO) 이런 오류가 계속 뜨고 다른 분들께서 질문하신 답변을 봐도 모르겠습니다... mysql 비밀번호는 확실하게 맞습니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
실무에서 swr redux질문
공부겸 swr로 작성한걸 redux로 변경해서 만들어봤는데 실무에서는 swr 하고 redux를 같이 사용한다고 하는데 어떤 경우를 예시로 들수 있을까요? 개인적인 생각으로는 비동기처리를 swr로 관리하고 동기처리를 redux로 관리하는 정도가 아닐까 싶은데
- 미해결Slack 클론 코딩[실시간 채팅 with React]
빌드할 때 질문이요
배포할 때 빌드해서 나온 결과물을 server 폴더 public에 넣어주면 될까요?이미 빌드된 프론트엔드 결과물이 있는 거 같은데 여기에다 제가 작업한 빌드된 결과물을 넣어주고배포하면 되는 건가요?저기 위해 dist란 폴더를 제가 작업한 결과물(빌드된)을넣으면 되는 거죠?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
react-router-dom@6 nested route
안녕하세요. "react-router-dom": "^6.3.0" 쓰고 있습니다. layouts/App/index.tsx에서 login의 경우 <Route path="/login" element={<LogIn />} /> 로 하면 브라우저에서 http://localhost:3090/login로 들어가면 화면이 잘 뜨는데, <Route path="/login/hi" element={<LogIn />} />로 하면 브라우저에서 http://localhost:3090/login/hi로 들어가면 화면이 안보입니다. 왜 path에 /를 두개 이상 넣으면 안될까요? 또한, https://www.inflearn.com/questions/417079 이분과 똑같이 해도 localhost:3090/workspace/dm 으로 들어가면 404 에러가 나오고 브라우저에서 흰페이지만 로딩 됩니다. https://reactrouter.com/en/v6.3.0/getting-started/tutorial 여기의 nested routes부분을 따라서 Outlet과 링크를 이용해서 똑같이 해봐도 링크를 누르면 localhost:3090/dm 으로 가지고, localhost:3090/workspace/dm으로 안가집니다.. 그래서 layouts/workspace/index.tsx에서 Link의 to="/dm"을 to="/workspace/dm"으로 바꾸고 localhost:3039/workspace 에서 dm 링크를 누르면 localhost:3039/workspace/dm로 가져서 잘 로딩되긴 하는데.. 리프레쉬 버튼을 누르면 다시 흰페이지만 나오고 404 에러뜹니다.. login 라우터도 /login은 되지만 /login/hi 이런식으로 /를 두개이상 붙이면 작동이 안되는데.. 제가 애초에 처음부터 세팅을 잘못한 걸까요..? ㅠ package.json 입니다 { "name": "slact-front", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "TS_NODE_PROJECT=\"tsconfig-for-webpack-config.json\" webpack serve --env development", "build": "TS_NODE_PROJECT=\"tsconfig-for-webpack-config.json\" NODE_ENV=production webpack", "lint": "eslint . --ext ts,tsx" }, "author": "", "license": "MIT", "dependencies": { "@emotion/babel-plugin": "^11.1.2", "@emotion/react": "^11.0.0", "@emotion/styled": "^11.0.0", "@jjordy/swr-devtools": "^2.0.7", "@loadable/component": "^5.14.1", "@types/autosize": "^4.0.0", "@types/gravatar": "^1.8.3", "@types/loadable__component": "^5.13.1", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", "@types/react-mentions": "^4.1.0", "@types/react-router-dom": "^5.3.3", "autosize": "^5.0.1", "axios": "^0.21.4", "core-js": "^3.14.0", "dayjs": "^1.10.4", "gravatar": "^1.8.2", "react": "^17.0.1", "react-custom-scrollbars-2": "^4.3.0", "react-dom": "^17.0.1", "react-mentions": "^4.1.1", "react-router": "^6.3.0", "react-router-dom": "^6.3.0", "react-toastify": "^8.0.2", "regexify-string": "^1.0.5", "socket.io-client": "^4.2.0", "swr": "^1.0.1", "typescript": "^4.4.2" }, "devDependencies": { "@babel/core": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", "@babel/preset-typescript": "^7.12.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-beta.4", "@types/fork-ts-checker-webpack-plugin": "^0.4.5", "@types/node": "^14.14.22", "@types/react-router": "^5.1.18", "@types/webpack": "^5.28.0", "@types/webpack-bundle-analyzer": "^4.4.0", "@types/webpack-dev-server": "^4.0.3", "@typescript-eslint/eslint-plugin": "^5.34.0", "@typescript-eslint/parser": "^5.34.0", "babel-loader": "^8.2.2", "css-loader": "^6.2.0", "eslint": "^7.18.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.2.0", "fork-ts-checker-webpack-plugin": "^6.1.0", "prettier": "^2.2.1", "react-refresh": "^0.10.0", "style-loader": "^3.2.1", "ts-node": "^10.2.1", "tsconfig-paths": "^3.9.0", "webpack": "^5.36.2", "webpack-bundle-analyzer": "^4.4.0", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.0.0" } }
- 미해결Slack 클론 코딩[실시간 채팅 with React]
콜백함수내에서 커스텀훅사용 가능여부
onclick같은 콜백함수내에서 커스텀훅을 사용할 수있나요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
500에러 질문
제로초님 깃헙코드 보고 리액트쿼리로 공부하다가 DM 보내는 로직에서 500에러가 떳습니다~!! api 문서대로 요청보냈고, 분명 서버쪽은 문제가 없을텐데 싶어서 며칠 째 고민하다 질문드려요!!! NaN으로 뜨는 부분이 백엔드 코드에서 콘솔 찍어보니까 req.query.perPage 이게 언디파인드로 전달 되더라구요. 근데 저는 perPage를 제로초님처럼 20으로 고정해서 전달하고 있는데 언디파인드로 뜨는게 이상하더라구요.ㅜㅜ +) 그리고 뮤테이션 쿼리키를 ["workspace", workspace, "dm", id, "chat"] 이렇게 주신 이유가 궁금해요!! 제가 공식문서 읽고 이해한게 배열로 줄 경우 첫번째 값이 캐싱할 때 쓰이는 이름이고 그 위에있는 애들은 mutation 안에서 사용될 외부 값을 넣어준다고 이해했었거든요!! 근데 dm 이나 chat은 사용되지 않는 것 같아서요!!
- 미해결Slack 클론 코딩[실시간 채팅 with React]
npx sequelize db:create 입력시 에러 발생
back 폴더에 npm i 이후 npx sequelize db:create 입력시 npm ERR! could not determine executable to run npm ERR! A complete log of this run can be found in:npm ERR! /Users/eycha/.npm/_logs/2022-08-21T06_14_10_186Z-debug-0.log 라는 에러 발생합니다. mysql 과 node 정상적으로 설치했는데 관련되서 검색해도 해결책이 없어서 질문 남깁니다.
- 해결됨Slack 클론 코딩[실시간 채팅 with React]
채널 관련해서 2가지 에러가 발생했는데 원인을 못찾겠습니다..
1. 로그인을 성공하면, 회원에 대한 정보도 정상적으로 가져오고 화면은 정상적으로 나오지만,useSWR을 통해 체널 데이터를 못가져오는 것 같습니다. 2. 채널 만들기를 하면 500 에러가 발생합니다. query: SELECT DISTINCT `distinctAlias`.`Users_id` AS `ids_Users_id` FROM (SELECT `Users`.`id` AS `Users_id`, `Users`.`email` AS `Users_email`, `Users`.`nickname` AS `U sers_nickname`, `Users__Users_Workspaces`.`id` AS `Users__Users_Workspaces_id`, `Users__Users_Workspaces`.`name` AS `Users__Users_Workspaces_name`, `Users__Users_Works paces`.`url` AS `Users__Users_Workspaces_url`, `Users__Users_Workspaces`.`createdAt` AS `Users__Users_Workspaces_createdAt`, `Users__Users_Workspaces`.`updatedAt` AS ` Users__Users_Workspaces_updatedAt`, `Users__Users_Workspaces`.`deletedAt` AS `Users__Users_Workspaces_deletedAt`, `Users__Users_Workspaces`.`OwnerId` AS `Users__Users_ Workspaces_OwnerId` FROM `users` `Users` LEFT JOIN `workspacemembers` `Users_Users__Users_Workspaces` ON `Users_Users__Users_Workspaces`.`UserId`=`Users`.`id` LEFT JOI N `workspaces` `Users__Users_Workspaces` ON `Users__Users_Workspaces`.`id`=`Users_Users__Users_Workspaces`.`WorkspaceId` AND (`Users__Users_Workspaces`.`deletedAt` IS NULL) WHERE ( (`Users`.`id` = ?) ) AND ( `Users`.`deletedAt` IS NULL )) `distinctAlias` ORDER BY `Users_id` ASC LIMIT 1 -- PARAMETERS: [5] query: SELECT `Users`.`id` AS `Users_id`, `Users`.`email` AS `Users_email`, `Users`.`nickname` AS `Users_nickname`, `Users__Users_Workspaces`.`id` AS `Users__Users_Wor kspaces_id`, `Users__Users_Workspaces`.`name` AS `Users__Users_Workspaces_name`, `Users__Users_Workspaces`.`url` AS `Users__Users_Workspaces_url`, `Users__Users_Worksp aces`.`createdAt` AS `Users__Users_Workspaces_createdAt`, `Users__Users_Workspaces`.`updatedAt` AS `Users__Users_Workspaces_updatedAt`, `Users__Users_Workspaces`.`dele tedAt` AS `Users__Users_Workspaces_deletedAt`, `Users__Users_Workspaces`.`OwnerId` AS `Users__Users_Workspaces_OwnerId` FROM `users` `Users` LEFT JOIN `workspacemember s` `Users_Users__Users_Workspaces` ON `Users_Users__Users_Workspaces`.`UserId`=`Users`.`id` LEFT JOIN `workspaces` `Users__Users_Workspaces` ON `Users__Users_Workspace s`.`id`=`Users_Users__Users_Workspaces`.`WorkspaceId` AND (`Users__Users_Workspaces`.`deletedAt` IS NULL) WHERE ( (`Users`.`id` = ?) ) AND ( `Users`.`deletedAt` IS NUL L ) AND ( `Users`.`id` IN (5) ) -- PARAMETERS: [5] user Users { id: 5, email: 'qqq123@naver.com', nickname: 'Quit', Workspaces: [ Workspaces { id: 1, name: 'Sleact', url: 'select', createdAt: 2022-08-20T04:04:09.828Z, updatedAt: 2022-08-20T04:04:09.828Z, deletedAt: null, OwnerId: null } ] } query: SELECT `Workspaces`.`id` AS `Workspaces_id`, `Workspaces`.`name` AS `Workspaces_name`, `Workspaces`.`url` AS `Workspaces_url`, `Workspaces`.`createdAt` AS `Work spaces_createdAt`, `Workspaces`.`updatedAt` AS `Workspaces_updatedAt`, `Workspaces`.`deletedAt` AS `Workspaces_deletedAt`, `Workspaces`.`OwnerId` AS `Workspaces_OwnerI d` FROM `workspaces` `Workspaces` WHERE ( (`Workspaces`.`url` = ?) ) AND ( `Workspaces`.`deletedAt` IS NULL ) LIMIT 1 -- PARAMETERS: ["sleact"] [Nest] 19948 - 2022. 08. 20. 오후 1:33:12 ERROR [ExceptionsHandler] Cannot read properties of null (reading 'id') TypeError: Cannot read properties of null (reading 'id') at ChannelsService.createWorkspaceChannels (C:\Users\kwa13\IdeaProjects\sleact\back\dist\main.js:2034:41) at processTicksAndRejections (node:internal/process/task_queues:96:5) at async C:\Users\kwa13\IdeaProjects\sleact\back\node_modules\@nestjs\core\router\router-execution-context.js:46:28 at async C:\Users\kwa13\IdeaProjects\sleact\back\node_modules\@nestjs\core\router\router-proxy.js:9:17 [Nest] 19948 - 2022. 08. 20. 오후 1:33:12 LOG [HTTP] POST /api/workspaces/sleact/channels 500 52 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (K HTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 ::ffff:127.0.0.1 ------------------------------------------------------------------------------------------------------------------------ DB 관련 스크린샷 - 채널 - 워크스페이스 ------------------------------------------------------------------------------------------- 백엔드 관련 코드 - channels.controller.ts @Controller('api/workspaces/:url/channels') export class ChannelsController { constructor(private readonly channelsService: ChannelsService) {} @ApiOperation({ summary: '채널 생성하기' }) @Post() async createChannel( @Param('url') url: string, @Body() body: CreateChannelDto, @User() user: Users, ) { return this.channelsService.createWorkspaceChannels( url, body.name, user.id, ); } - channels.service.ts async getWorkspaceChannels(url: string, myId: number) { return this.channelsRepository .createQueryBuilder('channels') .innerJoinAndSelect( 'channels.ChannelMembers', 'channelMembers', 'channelMembers.userId = :myId', { myId }, ) .innerJoinAndSelect( 'channels.Workspace', 'workspace', 'workspace.url = :url', { url }, ) .getMany(); } ----------------------------------------------------------------------------------------------------------------------- 프론트엔드 관련 코드 - Workspace/index.tsx const Workspace = () => { const [showUserMenu, setShowUserMenu] = useState(false); const [showCreateWorkspaceModal, setShowCreateWorkspaceModal] = useState(false); const [showWorkspaceModal, setShowWorkspaceModal] = useState(false); const [showCreateChannelModal, setShowCreateChannelModal] = useState(false); const [newWorkspace, onChangeNewWorkspace, setNewWorkspace] = useInput(''); const [newUrl, onChangeNewUrl, setNewUrl] = useInput(''); const { workspace } = useParams(); const { data: userData, error, mutate } = useSWR<IUser | false>('/api/users', fetcher); const { data: channelData } = useSWR<IChannel[]>(userData ? `/api/workspaces/${workspace}/channels` : null, fetcher); const onLogout = useCallback(() => { axios .post('/api/users/logout', null, { withCredentials: true, }) .then(() => { mutate(); }); }, []); const onClickUserProfile = useCallback(() => { setShowUserMenu((prev) => !prev); }, []); const onCloseUserProfile = useCallback((e: any) => { e.stopPropagation(); setShowUserMenu(false); }, []); const onClickCreateWorkspace = useCallback(() => { setShowCreateWorkspaceModal(true); }, []); const onCloseModal = useCallback(() => { setShowCreateWorkspaceModal(false); setShowCreateChannelModal(false); }, []); const onCreateWorkspace = useCallback( (e: any) => { e.preventDefault(); if (!newWorkspace || !newWorkspace.trim()) return; if (!newUrl || !newUrl.trim()) return; axios .post( '/api/workspaces', { name: newWorkspace, url: newUrl, }, { withCredentials: true, }, ) .then(() => { mutate(); setShowCreateWorkspaceModal(false); setNewWorkspace(''); setNewUrl(''); }) .catch((error) => { console.dir(error); toast.error(error.response?.data, { position: 'bottom-center' }); }); }, [newWorkspace, newUrl], ); const toggleWorkspaceModal = useCallback(() => { setShowWorkspaceModal((prev) => !prev); }, []); const onClickAddChannel = useCallback(() => { setShowCreateChannelModal(true); }, []); if (!userData) { return <Navigate replace to="/login" />; } return ( <div> <Header> <RightMenu> <span onClick={onClickUserProfile}> <ProfileImg src={gravatar.url(userData.email, { s: '28px', d: 'retro' })} alt={userData.nickname} /> {showUserMenu && ( <Menu style={{ right: 0, top: 38 }} show={showUserMenu} onCloseModal={onCloseUserProfile}> <ProfileModal> <img src={gravatar.url(userData.email, { s: '36px', d: 'retro' })} alt={userData.nickname} /> <div> <span id="profile-name">{userData.nickname}</span> <span id="profile-active">Active</span> </div> </ProfileModal> <LogOutButton onClick={onLogout}>로그아웃</LogOutButton> </Menu> )} </span> </RightMenu> </Header> <WorkspaceWrapper> <Workspaces> {userData?.Workspaces.map((ws) => { return ( <Link key={ws.id} to={`/workspace/sleact/channel/일반`}> <WorkspaceButton>{ws.name.slice(0, 1).toUpperCase()}</WorkspaceButton> </Link> ); })} <AddButton onClick={onClickCreateWorkspace}>+</AddButton>; </Workspaces> <Channels> <WorkspaceName onClick={toggleWorkspaceModal}>WorkspaceName</WorkspaceName> <MenuScroll> <Menu show={showWorkspaceModal} onCloseModal={toggleWorkspaceModal} style={{ top: 95, left: 80 }}> <WorkspaceModal> <h2>Sleact</h2> <button onClick={onClickAddChannel}>채널 만들기</button> <button onClick={onLogout}>로그아웃</button> </WorkspaceModal> </Menu> {channelData?.map((v) => ( <div>{v.name}</div> ))} </MenuScroll> </Channels> <Chats> <Routes> <Route path="/channel/:channel" element={<Channel />} /> <Route path="/dm/:id" element={<DM />} /> </Routes> </Chats> </WorkspaceWrapper> <Modal show={showCreateWorkspaceModal} onCloseModal={onCloseModal}> <form onSubmit={onCreateWorkspace}> <Label id="workspace-label"> <span>워크스페이스 이름</span> <Input id="workspace" value={newWorkspace} onChange={onChangeNewWorkspace} /> </Label> <Label id="workspace-url-label"> <span>워크스페이스 url</span> <Input id="workspace" value={newUrl} onChange={onChangeNewUrl} /> </Label> <Button type="submit">생성하기</Button> </form> </Modal> <CreateChannelModal show={showCreateChannelModal} onCloseModal={onCloseModal} setShowCreateChannelModal={setShowCreateChannelModal} /> </div> ); }; export default Workspace; - CreateChannelModal/index.tsx interface Props { show: boolean; onCloseModal: () => void; setShowCreateChannelModal: (flag: boolean) => void; } const CreateChannelModal: VFC<Props> = ({ show, onCloseModal, setShowCreateChannelModal }) => { const [newChannel, onChangeNewChannel, setNewChannel] = useInput(''); const { workspace, channel } = useParams(); const onCreateChannel = useCallback( (e: any) => { e.preventDefault(); axios .post( `/api/workspaces/${workspace}/channels`, { name: newChannel, }, { withCredentials: true }, ) .then(() => { setShowCreateChannelModal(false); setNewChannel(''); }) .catch((error) => { console.dir(error); toast.error(error.response?.data, { position: 'bottom-center' }); }); }, [newChannel], ); return ( <Modal show={show} onCloseModal={onCloseModal}> <form onSubmit={onCreateChannel}> <Label id="channel-label"> <span>채널</span> <Input id="channel" value={newChannel} onChange={onChangeNewChannel} /> </Label> <Button type="submit">생성하기</Button> </form> </Modal> ); }; export default CreateChannelModal; 스스로 해결해보려고 많은 시간을 들여봤지만, 도저히 모르겠네요..