39,600원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
workspace의 userData를 출력하면 undefined출력 이후 값이 나오는 현상
import { Channels, Chats, Header, LogOutButton, MenuScroll, ProfileImg, ProfileModal, RightMenu, WorkspaceName, Workspaces, WorkspaceWrapper } from '@layouts/Workspace/styles' import fetcher from '@utils/fetcher'; import React, { useCallback, useState } from 'react'; import useSWR from 'swr'; import axios from 'axios' import gravatar from 'gravatar' import { IUser } from '@typings/db'; import { Navigate, Route, Routes } from 'react-router'; import loadable from '@loadable/component'; import Menu from '@components/Menu'; const Channel = loadable(() => import('@pages/Ch')); const DirectMessage = loadable(() => import('@pages/DirectMessage')); const Workspace = () => { const [showUserMenu, setShowUserMenu] = useState(false); const { data: userData, error, mutate } = useSWR<any>('/api/users', fetcher, { dedupingInterval: 2000, }); console.log(">>" + userData) const onLogout = useCallback(() => { console.log(userData) axios.post( '/api/users/logout', null, { withCredentials: true, }) .then(() => { mutate(false, false); // revalidateUser(false); }) .catch((err) => { console.log(err) }) }, []) const onClickUserProfile = useCallback(() => { setShowUserMenu((prev) => !prev); }, []) if (userData === false) { return <Routes><Route path="/*" element={<Navigate replace to="/login" />} /></Routes> } return ( <div> <Header> <RightMenu> <span onClick={onClickUserProfile}> {/* {gravatar.url(userData.email, { s: '28px', d: 'retro' })} */} <ProfileImg src="" alt="" /> {/* toggle */} {showUserMenu && (<Menu style={{ right: 0, top: 38 }} show={showUserMenu} onCloseModal={onClickUserProfile}> <ProfileModal> <img src="" alt="" /> <div> {/* <span id="profile-name">{userData.nickame}</span> */} <span id="profile-name">name</span> <span id="profile-active">Active</span> </div> </ProfileModal> <LogOutButton onClick={onLogout}>로그아웃</LogOutButton> </Menu> )} </span> </RightMenu> </Header> <WorkspaceWrapper> <Workspaces>te</Workspaces> <Channels> <WorkspaceName>Sleact</WorkspaceName> <MenuScroll>Menu Scroll</MenuScroll> </Channels> <Chats> <Routes> <Route path='/workspace/channel' element={<Channel />} /> <Route path='/workspace/dm' element={<DirectMessage />} /> </Routes> </Chats> </WorkspaceWrapper> {/* {children} */} </div> ) } export default Workspace;workspaceimport axios from 'axios'; const fetcher = (url: string) => axios .get(url, { withCredentials: true, }) .then((response) => response.data); export default fetcher; fetcher.ts Workspace에서 콘솔로 userData를 찍으면 undefined가 나온 뒤 값이 출력 되는데 먼저 뜨는 undefined때문에 에러가 나서 gravatar아이콘을 못 넣고 있습니다.(창이 처음 뜰 때만 undefined가 1회 나온 뒤 값이 출력 되고 이후에는 계속 undefined없이 값만 정상 출력 됩니다.)
- 미해결Slack 클론 코딩[실시간 채팅 with React]
webpack 설정 후 index html 실행
setting ts 에서 글로벌로 npm i 하고 그 후 다시 강의하시는 경로러 이동후 npm run build 해도 이런 애러로 index html 에 아무런 글자가 보이지 않습니다 ㅠ
- 미해결Slack 클론 코딩[실시간 채팅 with React]
App.tsx 파일 npm run dev시 브라우저에 렌더링 안되는 현상 질문 드립니다!
질문들 다 찾아보고, 설정파일도 밀어보고, 에디터와 브라우저도 종료했다가 다 꺼보고 여러방면으로 시도해봤는데 브라우저에 렌더링이 되지 않아서 질문드립니다.App.tsx의 내용이 터미널에서 npm run dev로 구동시 화면에 렌더링 되지 않습니다.. 뭐가 잘못됐을까요?폴더 구조입니다.App.tsx 코드 부분입니다.client.tsx 코드 부분입니다.index.html 코드 부분입니다.npm run dev시 터미널 창입니다.Eun-Ng 🔥 ~/Documents/vscodeWorkspace/studying/slack_clone/front main npm run dev > sleact-ts-front@1.0.0 dev > webpack serve --env development <w> [webpack-dev-server] "hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration. <i> [webpack-dev-server] [HPM] Proxy created: /api/ -> http://localhost:3095 <i> [webpack-dev-server] Project is running at: <i> [webpack-dev-server] Loopback: http://localhost:3090/ <i> [webpack-dev-server] On Your Network (IPv4): http://192.168.0.12:3090/ <i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:3090/ <i> [webpack-dev-server] Content not from webpack is served from '/Users/eun-ng/Documents/vscodeWorkspace/studying/slack_clone/front' directory <i> [webpack-dev-server] 404s will fallback to '/index.html' @babel/preset-env: `DEBUG` option Using targets: { "chrome": "106" } Using modules transform: auto Using plugins: syntax-class-static-block syntax-private-property-in-object syntax-class-properties syntax-numeric-separator syntax-nullish-coalescing-operator syntax-optional-chaining syntax-json-strings syntax-optional-catch-binding syntax-async-generators syntax-object-rest-spread syntax-dynamic-import proposal-export-namespace-from { } syntax-top-level-await Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set. asset app.js 1.55 MiB [emitted] (name: app) runtime modules 27.9 KiB 13 modules modules by path ./node_modules/ 1.37 MiB 89 modules ./client.tsx 2.85 KiB [built] [code generated] ./layouts/App.tsx 2.76 KiB [built] [code generated] sleact (webpack 5.74.0) compiled successfully in 1504 ms브라우저 구동 화면입니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
채널 생성시 channelData.map is not a function
채널생성 클릭하면 channeldata.map is not a function이라고 에러가 뜨는데channelData뿌려지는곳에 ?옵셔널도 줬고..아래처럼 잘 작성한것같은데 어딜 놓쳤는지 모르겠습니다.새로고침하면 추가된 채널명이 출력됩니다. workspaceimport fetcher from '@utils/fetcher'; import axios from 'axios'; import React, { FC, useCallback, useState } from 'react'; import { Navigate, useParams } from 'react-router-dom'; import useSWR from 'swr'; import { AddButton, Channels, Chats, Header, LogOutButton, MenuScroll, ProfileImg, ProfileModal, RightMenu, WorkspaceButton, WorkspaceModal, WorkspaceName, Workspaces, WorkspaceWrapper, } from './styles'; import gravatar from 'gravatar'; import Menu from '@components/menu'; import { Link } from 'react-router-dom'; import { IChannel, IUser, IWorkspace } from '@typings/db'; import { Button, Input, Label } from '@pages/signup/styles'; import useInput from '@hooks/useInput'; import Modal from '@components/modal'; import { toast } from 'react-toastify'; import CreateChannelModal from '@components/createChannelModal'; const Workspace: FC = ({ children }) => { const { workspace, channel } = useParams<{ workspace: string; channel: string }>(); const { data: userData, error, mutate } = useSWR<IUser | false>('/api/users', fetcher, { dedupingInterval: 2000 }); const { data: channelData } = useSWR<IChannel[]>(userData ? `/api/workspaces/${workspace}/channels` : null, fetcher); if (!userData) { return <Navigate to="/login" />; } const [showUserMenu, setShowUserMenu] = useState(false); const [newWorkspace, onChangeNewWorkspace, setNewWorkspace] = useInput(''); const [newUrl, onChangeNewUrl, setNewUrl] = useInput(''); const [showWorkspaceModal, setShowWorkspaceModal] = useState(false); const [showCreateChannelModal, setShowCreateChannelModal] = useState(false); const [showCreateWorkspaceModal, setShowCreateWorkspaceModal] = useState(false); //functions const onLogout = useCallback(() => { axios .post('/api/users/logout', null, { withCredentials: true, }) .then((res) => { mutate(res.data); }); }, []); const onClickUserProfile = useCallback(() => { setShowUserMenu(!showUserMenu); }, [showUserMenu]); const onClickCreateWorkspace = useCallback(() => { setShowCreateWorkspaceModal(true); }, []); const onCreateWorkspace = useCallback( (e) => { e.preventDefault(); if (!newWorkspace || !newWorkspace.trim()) return; if (!newUrl || !newUrl.trim()) return; //trim ->띄어쓰기 하나도 통과 돼버리는걸 막는다. axios .post( '/api/workspaces', { workspace: newWorkspace, url: newUrl, }, { withCredentials: true, }, ) .then((res) => { mutate(res.data); setShowCreateWorkspaceModal(false); setNewWorkspace(''), setNewUrl(''); }) .catch((err) => { console.dir(err); toast.error(error.response?.data, { position: 'bottom-center' }); }); }, [newWorkspace, newUrl], ); const onCloseModal = useCallback(() => { setShowCreateWorkspaceModal(false); setShowCreateChannelModal(false); }, []); const toggleWorkspaceModal = useCallback(() => { setShowWorkspaceModal(!showWorkspaceModal); }, [showWorkspaceModal]); const onClickAddChannel = useCallback(() => { setShowCreateChannelModal(true); }, []); 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 }} onCloseModal={onClickUserProfile} show={showUserMenu}> <ProfileModal> <img src={gravatar.url(userData.email, { s: '28px', 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: IWorkspace) => { return ( <Link key={ws.id} to={`/workspace/${123}/channel/일반`}> <WorkspaceButton>{ws.name.slice(0, 1).toUpperCase()}</WorkspaceButton> </Link> ); })} <AddButton onClick={onClickCreateWorkspace}>+</AddButton> </Workspaces> <Channels> <WorkspaceName onClick={toggleWorkspaceModal}>Sleact</WorkspaceName> <MenuScroll> <Menu show={showWorkspaceModal} onCloseModal={toggleWorkspaceModal} style={{ top: 95, left: 80 }}> <WorkspaceModal> <h2>Sleact</h2> {/* <button onClick={onClickInviteWorkspace}>워크스페이스에 사용자 초대</button> */} <button onClick={onClickAddChannel}>채널 만들기</button> <button onClick={onLogout}>로그아웃</button> </WorkspaceModal> </Menu> {channelData?.map((v, idx) => ( <div key={idx}>{v.name}</div> ))} </MenuScroll> </Channels> <Chats> {children}</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; createChannelModalimport Modal from '@components/modal'; import useInput from '@hooks/useInput'; import { Button, Input, Label } from '@pages/signup/styles'; import { IChannel, IUser } from '@typings/db'; import fetcher from '@utils/fetcher'; import axios from 'axios'; import React, { useCallback, VFC } from 'react'; import { useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; import useSWR from 'swr'; 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<{ workspace: string; channel: string }>(); const { data: userData } = useSWR<IUser | false>(`/api/users`, fetcher); const { data: channelData, mutate } = useSWR<IChannel[]>( userData ? `/api/workspaces/${workspace}/channels` : null, fetcher, ); const onCreateChannel = useCallback( (e) => { e.preventDefault(); axios .post( `/api/workspaces/${workspace}/channels`, { name: newChannel, }, { withCredentials: true }, ) .then((res) => { setShowCreateChannelModal(false); mutate(res.data); setNewChannel(''); }) .catch((err) => { console.dir(err); toast.error(err.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;
- 미해결Slack 클론 코딩[실시간 채팅 with React]
로그인 -> workspace로 이동하지 않는 이슈
안녕하세요 !강의 잘 듣고 있습니다.아래처럼 작성했는데 로그인 성공했는데 workspace로 가지지 않아서요.console.log(data) 찍어보니데이터가 들어왔다가 초기화 돼버리는데 그래서 이동이 안되는 것 같은데 원인을 잘 모르겠습니다.ㅜㅜpages_channel_index_tsx 어쩌고가 response에 뜨는데 혹시 연관있을까 해서 첨부합니다.. dimport useInput from '@hooks/useInput'; import React, { useCallback, useState } from 'react'; import { Header, Button, Error, Form, Input, Label, LinkContainer, Success } from '@pages/signup/styles'; import { Link, Navigate } from 'react-router-dom'; import axios from 'axios'; import useSWR from 'swr'; import fetcher from '@utils/fetcher'; const LogIn = () => { const { data, error, mutate } = useSWR('/api/users', fetcher); const [logInError, setLogInError] = useState(false); const [email, onChangeEmail] = useInput(''); const [password, onChangePassword] = useInput(''); const onSubmit = useCallback( (e) => { e.preventDefault(); setLogInError(false); axios .post( '/api/users/login', { email, password }, { withCredentials: true, }, //get일때와 post일때 withCredentials 위치가 다르다. ) .then((response) => { mutate(response.data); }) .catch((error) => { setLogInError(error.response?.data?.statusCode === 401); }); }, [email, password], ); console.log(data, '데이타'); if (data) { return <Navigate to="/workspace/channel" />; } return ( <div id="container"> <Header>Sleact</Header> <Form onSubmit={onSubmit}> <Label id="email-label"> <span>이메일 주소</span> <div> <Input type="email" id="email" name="email" value={email} onChange={onChangeEmail} /> </div> </Label> <Label id="password-label"> <span>비밀번호</span> <div> <Input type="password" id="password" name="password" value={password} onChange={onChangePassword} /> </div> {logInError && <Error>이메일과 비밀번호 조합이 일치하지 않습니다.</Error>} </Label> <Button type="submit">로그인</Button> </Form> <LinkContainer> 아직 회원이 아니신가요? <Link to="/signup">회원가입 하러가기</Link> </LinkContainer> </div> ); }; export default LogIn; import fetcher from '@utils/fetcher'; import axios from 'axios'; import React, { FC, useCallback } from 'react'; import { Navigate } from 'react-router-dom'; import useSWR from 'swr'; const Workspace: FC = ({ children }) => { const { data, error, mutate } = useSWR('/api/users', fetcher); const onLogout = useCallback(() => { axios .post('/api/users/logout', null, { withCredentials: true, }) .then((res) => { mutate(res.data); }); }, []); if (!data) { return <Navigate to="/login" />; } return ( <div> <button onClick={onLogout}>로그아웃</button> {children} </div> ); }; export default Workspace;
- 미해결Slack 클론 코딩[실시간 채팅 with React]
타입스크립트 질문
첫번째 사진의 빨간색 타입스크립트 코드와 두번째 사진의 <number[]>([])가 무슨 뜻인지 알려주실 수 있을까요,,,두번째는 숫자객체 타입라는 것 같은데 ([])는 왜붙어있는걸까요,, 그리고 타입 찾는게 너무 헷갈리는데 어떻게 공부해야할까요? 타입을 안적으면 자꾸 에러가 나서요 ㅜ
- 미해결Slack 클론 코딩[실시간 채팅 with React]
axios.post에서 로컬호스트 주소를 지우면 404 에러가 나옵니다
제로초님 처럼 axios에서 http://localhost:3095 를 지우니 404에러가 뜹니다.그리고 제로초님이 프록시 부분에 주석처리를 해서 보여주시는 부분은 따라하지 않았습니다 콘솔에서 나오는 에러입니다백엔드 터미널에서는 에러가 뜨지 않는거 같습니다코드 입니다import React, { useState, useCallback } from "react"; import axios from "axios"; import { Success, Form, Error, Label, Input, LinkContainer, Button, Header } from "./SignpStyles"; import useInput from "@hooks/useInput"; const SignUp = () => { const [ email, onChangeEmail, setEmail ] = useInput(""); const [ nickname, onChangeNickname, setNickname ] = useInput(""); const [ password, setPasswrod ] = useState(""); const [ passwordCheck, setPasswordCheck ] = useState(""); const [ mismatchError, setMismatchError ] = useState(false); const onSubmit = useCallback( (e) => { e.preventDefault(); axios .post("/api/users", { email, nickname, password }) .then((response) => { console.log(response); }) .catch((err) => { console.log(err.response); }); }, [ email, nickname, password, passwordCheck ] ); const onChangePassword = useCallback( (e) => { setPasswrod(e.target.value); setMismatchError(e.target.value !== passwordCheck); }, [ passwordCheck ] ); const onChangePasswordCheck = useCallback( (e) => { setPasswordCheck(e.target.value); setMismatchError(e.target.value !== password); }, [ password ] ); return ( <div id="container"> <Header>slack</Header> <Form onSubmit={onSubmit}> <Label id="email-label"> <span>이메일 주소</span> <div> <Input type="email" name="email" id="email" value={email} onChange={onChangeEmail} /> </div> </Label> <Label id="nickname-label"> <span>닉네임</span> <div> <Input type="text" name="nickname" id="nickname" value={nickname} onChange={onChangeNickname} /> </div> </Label> <Label id="password-label"> <span>비밀번호</span> <div> <Input type="password" name="password" id="password" value={password} onChange={onChangePassword} /> </div> </Label> <Label id="password-check-label"> <span>비밀번호 확인</span> <div> <Input type="password" name="password-check" id="password-check" value={passwordCheck} onChange={onChangePasswordCheck} /> </div> {mismatchError && <Error>비밀번호가 일치하지 않습니다</Error>} {!nickname && <Error>닉네임이 비어있습니다</Error>} {!email && <Error>이메일이 비어있습니다</Error>} </Label> <Button type="submit">회원가입</Button> </Form> </div> ); }; export default SignUp; 혼자서 해결해 보고 싶었는데 어디서 문제가 생기는 건지 모르겠어서 이렇게 질문을 남깁니다
- 미해결Slack 클론 코딩[실시간 채팅 with React]
socket.io 버전 업그레이드 연결 오류가 납니다.
안녕하세요 제로초님.제가 socket.io 이벤트 연결하기 강의를 보다가 해당 에러가 발생하게 되어 질문 드립니다. 버전은 이렇게 되고// 프론트 버전 "socket.io-client": "^4.5.2", // 백엔드 버전 "socket.io": "^4.5.2" 프론트 useSocket 의 소스는이렇게 되어 있고 백엔드 소스는이렇게 되어 있습니다. 연결시해당 에러가 발생합니다.혹시 이유를 알 수 있을까요??
- 미해결Slack 클론 코딩[실시간 채팅 with React]
npm i 오류
npm ERR! code ELIFECYCLEnpm ERR! errno 9009npm ERR! bcrypt@5.0.0 install: node-pre-gyp install --fallback-to-buildnpm ERR! Exit status 9009npm ERR!npm ERR! Failed at the bcrypt@5.0.0 install script.npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! A complete log of this run can be found in:npm ERR! C:\Users\swcha\AppData\Roaming\npm-cache\_logs\2022-10-13T02_53_14_535Z-debug.log 깃 클론 받고 back 폴더에서 npm i 했는데 저런 오류가 발생했습니다.구글링했는데 잘 모르겠어서 질문 드립니다!
- 미해결Slack 클론 코딩[실시간 채팅 with React]
회원가입 버튼을 누르면 백엔드 서버 에러 500번대 에러가 뜹니다
제가 백엔드서버를 제대로 세팅을 하지 않은건지 회원가입 버튼을 누르면 500번 에러가 뜹니다아래는 회원가입 코드 입니다import React, { useState, useCallback } from "react"; import axios from "axios"; import { Success, Form, Error, Label, Input, LinkContainer, Button, Header } from "./styles"; import useInput from "@hooks/useinput"; const SignUp = () => { const [ email, onChangeEmail, setEmail ] = useInput(""); const [ nickname, onChangeNickname, setNickname ] = useInput(""); const [ password, setPasswrod ] = useState(""); const [ passwordCheck, setPasswrodCheck ] = useState(""); const [ mismatchError, setMismatchError ] = useState(false); const onSubmit = useCallback( (e) => { e.preventDefault(); if (!mismatchError) { console.log("회원가입 하기"); axios .post("http://localhost:3095/api/users", { email, password, nickname }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err.res); }); } }, [ email, nickname, password, passwordCheck, mismatchError ] ); const onChangePassword = useCallback( (e) => { setPasswrod(e.target.value); setMismatchError(e.target.value !== passwordCheck); }, [ passwordCheck ] ); const onChangePasswordCheck = useCallback( (e) => { setPasswrodCheck(e.target.value); setMismatchError(e.target.value !== password); }, [ password ] ); return ( <div> <Header>Sleact</Header> <Form onSubmit={onSubmit}> <Label id="email-label"> <span>이메일 주소</span> <div> <Input type="email" id="email" value={email} onChange={onChangeEmail} /> </div> </Label> <Label id="nickname-label"> <span>닉네임</span> <div> <Input type="text" id="nickname" value={nickname} onChange={onChangeNickname} /> </div> </Label> <Label> <span>비밀번호</span> <div> <Input type="password" id="password" value={password} onChange={onChangePassword} /> </div> </Label> <Label> <span>비밀번호 확인</span> <div> <Input type="password" id="password-check" value={passwordCheck} onChange={onChangePasswordCheck} /> </div> {mismatchError && <Error>비밀번호가 일치하지 않습니다</Error>} {!nickname && <Error>닉네임이 비어있습니다</Error>} </Label> <Button type="submit">회원가입</Button> </Form> </div> ); }; export default SignUp; 만약 백엔드 서버 설정이 잘못된거면 다시 0강 부터 보고 오겠습니다
- 미해결Slack 클론 코딩[실시간 채팅 with React]
proxy 504 에러
몇 번 재확인 해도 webpack proxy 설정을 잘 한 것 같은데혹시 proxy 설정을 보여주신대로 하면 network에 Request URL이 3095로 되는게 정상 아닌가요?dev server 껐다 다시 실행, localhost를 127.0.0.1로 바꿔서 실행도 해봤습니다. 웹팩이 제대로 연결(?)이 안된건지.. 504 Gateway timeout은 새로고침하거나 네트워크 장치를 다시시작하라는데,. 좀 더 구글링 해보겠습니다. 근데 이게 근본적인 원인은 아닌것같아서.. 질문남깁니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
npm i 오류
안녕하세요 npm i 오류가 떠서요.. 열심히 찾아밨는데 안되서 왔습니다 PS C:\Users\DELL\Desktop\sleact\back> npm inpm ERR! code 1npm ERR! path C:\Users\DELL\Desktop\sleact\back\node_modules\bcryptnpm ERR! command failednpm ERR! command C:\WINDOWS\system32\cmd.exe /d /s /c C:\Users\DELL\AppData\Local\Temp\install-913cc4ae.cmdnpm ERR! gyp info it worked if it ends with oknpm ERR! gyp info using node-gyp@9.0.0npm ERR! gyp info using node@16.17.1 | win32 | x64npm ERR! gyp info find Python using Python version 3.10.7 found at "C:\Users\DELL\AppData\Local\Programs\Python\Python310\python.exe"npm ERR! gyp info find VS using VS2019 (16.11.32126.315) found at:npm ERR! gyp info find VS "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community"npm ERR! gyp info find VS run with --verbose for detailed informationnpm ERR! gyp info spawn C:\Users\DELL\AppData\Local\Programs\Python\Python310\python.exenpm ERR! gyp info spawn args [npm ERR! gyp info spawn args 'C:\\Program Files\\nodejs\\node_modules\\npm\\node_modules\\node-gyp\\gyp\\gyp_main.py',npm ERR! gyp info spawn args 'binding.gyp',npm ERR! gyp info spawn args '-f',npm ERR! gyp info spawn args 'msvs',npm ERR! gyp info spawn args '-I',npm ERR! gyp info spawn args 'C:\\Users\\DELL\\Desktop\\sleact\\back\\node_modules\\bcrypt\\build\\config.gypi',npm ERR! gyp info spawn args '-I',npm ERR! gyp info spawn args 'C:\\Program Files\\nodejs\\node_modules\\npm\\node_modules\\node-gyp\\addon.gypi',npm ERR! gyp info spawn args '-I',npm ERR! gyp info spawn args 'C:\\Users\\DELL\\AppData\\Local\\node-gyp\\Cache\\16.17.1\\include\\node\\common.gypi',npm ERR! gyp info spawn args '-Dlibrary=shared_library',npm ERR! gyp info spawn args '-Dvisibility=default',npm ERR! gyp info spawn args '-Dnode_root_dir=C:\\Users\\DELL\\AppData\\Local\\node-gyp\\Cache\\16.17.1',npm ERR! gyp info spawn args '-Dnode_gyp_dir=C:\\Program Files\\nodejs\\node_modules\\npm\\node_modules\\node-gyp',npm ERR! gyp info spawn args '-Dnode_lib_file=C:\\\\Users\\\\DELL\\\\AppData\\\\Local\\\\node-gyp\\\\Cache\\\\16.17.1\\\\<(target_arch)\\\\node.lib',npm ERR! gyp info spawn args '-Dmodule_root_dir=C:\\Users\\DELL\\Desktop\\sleact\\back\\node_modules\\bcrypt',npm ERR! gyp info spawn args '-Dnode_engine=v8',npm ERR! gyp info spawn args '--depth=.',npm ERR! gyp info spawn args '--no-parallel',npm ERR! gyp info spawn args '--generator-output',npm ERR! gyp info spawn args 'C:\\Users\\DELL\\Desktop\\sleact\\back\\node_modules\\bcrypt\\build',npm ERR! gyp info spawn args '-Goutput_dir=.'npm ERR! gyp info spawn args ]npm ERR! gyp: Undefined variable module_name in binding.gyp while trying to load binding.gypnpm ERR! gyp ERR! configure errornpm ERR! gyp ERR! stack Error: gyp failed with exit code: 1npm ERR! gyp ERR! stack at ChildProcess.onCpExit (C:\Program Files\nodejs\node_modules\npm\node_modules\node-gyp\lib\configure.js:261:16)npm ERR! gyp ERR! stack at ChildProcess.emit (node:events:513:28)npm ERR! gyp ERR! System Windows_NT 10.0.19044npm ERR! gyp ERR! command "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js" "rebuild" npm ERR! gyp ERR! cwd C:\Users\DELL\Desktop\sleact\back\node_modules\bcryptnpm ERR! gyp ERR! node -v v16.17.1npm ERR! gyp ERR! not oknpm ERR! A complete log of this run can be found in:npm ERR! C:\Users\DELL\AppData\Local\npm-cache\_logs\2022-10-10T08_54_05_058Z-debug-0.log
- 해결됨Slack 클론 코딩[실시간 채팅 with React]
주소 설계 원리 질문
해당 부분 (df) 클릭 시, url에도 df가 뜹니다. 그리곤 DM리스트를 만들 때는, //왼쪽 디엠리스트 const DMList: FC = () => { const { workspace } = useParams<{ workspace?: string }>(); //워크스페이스에 참여해있는 멤버들을 다 불러온다. const { data: userData, error, mutate, } = useSWR<IUser>('http://localhost:3095/api/users', fetcher, { dedupingInterval: 2000, // 2초 }); const { data: memberData } = useSWR<IUserWithOnline[]>( userData ? `http://localhost:3095/api/workspaces/${workspace}/members` : null, fetcher, );useParams 를 이용해 url에 있는 workspace 명을 가져오고 이를 통해 memberData를 요청합니다.제가 궁금한 부분은, 첫 번째 사진의 'df'를 클릭했을 때 주소창에 http://localhost:3090/workspace/sleact/channel/df 이런식으로 뜨게 할 수 있는 부분의 코드가 어디에 작성되어 있는지 입니다. workspace >index.tsx <Routes> <Route path="/channel/:channel" element={<Channel />} /> <Route path="/dm/:id" element={<DirectMessage />} /> </Routes>이 부분인 것 같은데,, 그럼 :channel에 해당 채널의 이름이 들어가겠죠? 근데 그럼, 해당 채널의 이름이 :channel에 들어가게 되는 로직은 또 어디있는 건가요..? 변수, 라우터, api에 대한 부분이 계속 꼬이다 보니 정리가 안됩니다 ㅜㅜ 설명해주시면 감사하겠습니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
i 태크 classname 관련해서 질문
안녕하세요. 11분30초쯤 DMList를 만들 때 i태그의 클래스들은 전부 슬랙에서 그대로 가져오셨다고 하는 말이 이해가 되지 않아서 질문드립니다.보통은 css파일에 .box 로 클래스를 지정해서 만들면jsx에서 사용할때 div className="box" > 이런 식으로 사용하잖아요??근데 강의에서 사용한 class들은 전부 어디에서 오는건가요??index.html에 있는 link에 가져온 파일이 css를 전부 가지고 있는건가요?
- 해결됨Slack 클론 코딩[실시간 채팅 with React]
안녕하세요. EACCES permission 에러 질문드립니다.
회사컴퓨터에서 강의를 듣다가 노트북으로 코드를 옴겨왔는데 갑자기이런 에러가 나네요 구글링을 해보니 접근권한 혹은 이미 사용중인 포트라고 하는데켜진 로컬호스트 포트중엔 3090이 쓰이는건 없었습니다.
- 해결됨Slack 클론 코딩[실시간 채팅 with React]
해당에러는 백엔드 문제인가요?
proxy서버를 켜도 404에러가 뜹니다.. 들어가면 존재하지 않는 워크페이스라고 합니다 조언을 얻을 수 있을까요,, 어느 파일에서 잘못된건지 모르겠어요밑 코드는 workspace 코드입니다. 프록시서버가 안먹혀서 http는 수동으로 다 붙여놓은 상태입니다. import ChannelList from '@components/ChannelList'; import DMList from '@components/DMList'; import InviteChannelModal from '@components/InviteChannelModal'; import InviteWorkspaceModal from '@components/InviteWorkspaceModal'; import Menu from '@components/Menu'; import Modal from '@components/Modal'; import useInput from '@hooks/useInput'; import { AddButton, Channels, Chats, Header, LogOutButton, MenuScroll, ProfileImg, ProfileModal, RightMenu, WorkspaceButton, WorkspaceModal, WorkspaceName, Workspaces, WorkspaceWrapper, } from '@layouts/Workspace/styles'; import loadable from '@loadable/component'; import { Button, Input, Label } from '@pages/SignUp/styles'; import { IChannel, IUser } from '@typings/db'; import fetcher from '@utils/fetcher'; import axios from 'axios'; import React, { VFC, useCallback, useState, useEffect } from 'react'; import { Navigate, Routes, useParams } from 'react-router'; import { Link, Route } from 'react-router-dom'; import useSWR from 'swr'; import gravatar from 'gravatar'; import { toast } from 'react-toastify'; import CreateChannelModal from '@components/CreateChannelModal'; const Channel = loadable(() => import('@pages/Channel')); const DirectMessage = loadable(() => import('@pages/DirectMessage')); const Workspace: VFC = () => { const [showUserMenu, setShowUserMenu] = useState(false); const [showCreateWorkspaceModal, setShowCreateWorkspaceModal] = useState(false); const [showInviteWorkspaceModal, setShowInviteWorkspaceModal] = useState(false); const [showInviteChannelModal, setShowInviteChannelModal] = useState(false); const [showWorkspaceModal, setShowWorkspaceModal] = useState(false); const [showCreateChannelModal, setShowCreateChannelModal] = useState(false); const [newWorkspace, onChangeNewWorkspace, setNewWorkpsace] = useInput(''); const [newUrl, onChangeNewUrl, setNewUrl] = useInput(''); const { workspace } = useParams<{ workspace: string }>(); const { data: userData, error, mutate, } = useSWR<IUser | false>('http://localhost:3095/api/users', fetcher, { dedupingInterval: 2000, // 2초 }); const { data: channelData } = useSWR<IChannel[]>( userData ? `http://localhost:3095/api/workspaces/${workspace}/channels` : null, fetcher, ); const { data: memberData } = useSWR<IUser[]>( userData ? `http://localhost:3095/api/workspaces/${workspace}/members` : null, fetcher, ); const onLogout = useCallback(() => { axios .post('http://localhost:3095/api/users/logout', null, { withCredentials: true, }) .then(() => { mutate(false, false); }); }, []); const onCloseUserProfile = useCallback((e) => { e.stopPropagation(); setShowUserMenu(false); }, []); const onClickUserProfile = useCallback(() => { setShowUserMenu((prev) => !prev); }, []); const onClickCreateWorkspace = useCallback(() => { setShowCreateWorkspaceModal(true); }, []); const onCreateWorkspace = useCallback( (e) => { e.preventDefault(); if (!newWorkspace || !newWorkspace.trim()) return; if (!newUrl || !newUrl.trim()) return; axios .post( 'http://localhost:3095/api/workspaces', { workspace: newWorkspace, url: newUrl, }, { withCredentials: true, }, ) .then(() => { mutate(); setShowCreateWorkspaceModal(false); setNewWorkpsace(''); setNewUrl(''); }) .catch((error) => { console.dir(error); toast.error(error.response?.data, { position: 'bottom-center' }); }); }, [newWorkspace, newUrl], ); const onCloseModal = useCallback(() => { setShowCreateWorkspaceModal(false); setShowCreateChannelModal(false); setShowInviteWorkspaceModal(false); setShowInviteChannelModal(false); }, []); const toggleWorkspaceModal = useCallback(() => { setShowWorkspaceModal((prev) => !prev); }, []); const onClickAddChannel = useCallback(() => { setShowCreateChannelModal(true); }, []); const onClickInviteWorkspace = useCallback(() => { setShowInviteWorkspaceModal(true); }, []); if (!userData) { return <Navigate 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.nickname, { 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/${123}/channel/일반`}> <WorkspaceButton>{ws.name.slice(0, 1).toUpperCase()}</WorkspaceButton> </Link> ); })} <AddButton onClick={onClickCreateWorkspace}>+</AddButton> </Workspaces> <Channels> <WorkspaceName onClick={toggleWorkspaceModal}>Sleact</WorkspaceName> <MenuScroll> <Menu show={showWorkspaceModal} onCloseModal={toggleWorkspaceModal} style={{ top: 95, left: 80 }}> <WorkspaceModal> <h2>Sleact</h2> <button onClick={onClickInviteWorkspace}>워크스페이스에 사용자 초대</button> <button onClick={onClickAddChannel}>채널 만들기</button> <button onClick={onLogout}>로그아웃</button> </WorkspaceModal> </Menu> <ChannelList /> <DMList /> </MenuScroll> </Channels> <Chats> <Routes> <Route path="/workspace/channel/:channel" element={Channel} /> <Route path="/workspace/dm/:id" element={DirectMessage} /> </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} /> <InviteWorkspaceModal show={showInviteWorkspaceModal} onCloseModal={onCloseModal} setShowInviteWorkspaceModal={setShowInviteWorkspaceModal} /> <InviteChannelModal show={showInviteChannelModal} onCloseModal={onCloseModal} setShowInviteChannelModal={setShowInviteChannelModal} /> </div> ); }; export default Workspace;
- 미해결Slack 클론 코딩[실시간 채팅 with React]
답변 부탁드립니다..
https://www.inflearn.com/questions/663973 못읽고 지나치신 거 같아요,,ㅜ
- 미해결Slack 클론 코딩[실시간 채팅 with React]
안녕하세요. invailhost header 질문
현재 슬랙클론 수강중인 학생입니다.저는 군대에서 독학으로 공부중입니다.현재 제로초님 깃허브에서 git clone 하여 그대로 데모버전을 사용하고 있습니다.제가 사용하는 에디터는 구름ide를 사용하고 있는데구름 ide 에서는 localhost를 제공해주지 않고, 제가 port를 설정하면 구름에서 제공해주는 사이트 주소를 사용하는 식으로 운영됩니다.제가 본 영상에서와 같이 dependency까지 그대로 다운받고,npm run dev 명령어를 실행하면 당연히 localhost가 없기때문에 사이트 접속이 힘듭니다....그래서 port 3090에 맞게 url에 접속하면 invaild host header 에러가 나옵니다..전에 위 에러를 만났을 때는 항상 cra로 개발했기 때문에 금방 구글링으로 해결을 했는데 현재는 어떻게 해결해야 할지 모르는 상황입니다..
- 해결됨Slack 클론 코딩[실시간 채팅 with React]
로그인시 데이터를 못 가져옵니다. 500에러
네트워크에서 확인시 500에러가 뜹니다. 요청하는 url이 프론트 주소라 그런건지.. 로그인 코드import useInput from '@hooks/useInput'; import { Success, Form, Error, Label, Input, LinkContainer, Button, Header } from '@pages/SignUp/styles'; import fetcher from '@utils/fetcher'; import axios from 'axios'; import React, { useCallback, useState } from 'react'; import { Link, Navigate } from 'react-router-dom'; import useSWR from 'swr'; const LogIn = () => { const { data, error, mutate } = useSWR('/api/users', fetcher); const [logInError, setLogInError] = useState(false); const [email, onChangeEmail] = useInput(''); const [password, onChangePassword] = useInput(''); const onSubmit = useCallback( (e) => { e.preventDefault(); setLogInError(false); axios .post( '/api/users/login', { email, password }, { withCredentials: true, }, ) .then((response) => { mutate(); }) .catch((error) => { setLogInError(error.response?.data?.statusCode === 401); }); }, [email, password], ); if (data === undefined) { return <div>로딩중...</div>; } if (data) { return <Navigate replace to="/workspace/sleact/channel/일반" />; } // console.log(error, userData); // if (!error && userData) { // console.log('로그인됨', userData); // return <Redirect to="/workspace/sleact/channel/일반" />; // } return ( <div id="container"> <Header>Sleact</Header> <Form onSubmit={onSubmit}> <Label id="email-label"> <span>이메일 주소</span> <div> <Input type="email" id="email" name="email" value={email} onChange={onChangeEmail} /> </div> </Label> <Label id="password-label"> <span>비밀번호</span> <div> <Input type="password" id="password" name="password" value={password} onChange={onChangePassword} /> </div> {logInError && <Error>이메일과 비밀번호 조합이 일치하지 않습니다.</Error>} </Label> <Button type="submit">로그인</Button> </Form> <LinkContainer> 아직 회원이 아니신가요? <Link to="/signup">회원가입 하러가기</Link> </LinkContainer> </div> ); }; export default LogIn; 회원가입 코드import useInput from '@hooks/useInput'; import fetcher from '@utils/fetcher'; import React, { useCallback, useState, VFC } from 'react'; import axios from 'axios'; import useSWR from 'swr'; import { Success, Form, Error, Label, Input, LinkContainer, Button, Header } from './styles'; import { Link, Navigate } from 'react-router-dom'; const SignUp = () => { const { data, error, mutate } = useSWR('/api/users', fetcher); const [email, onChangeEmail] = useInput(''); const [nickname, onChangeNickname] = useInput(''); const [password, , setPassword] = useInput(''); const [passwordCheck, , setPasswordCheck] = useInput(''); const [mismatchError, setMismatchError] = useState(false); const [signUpError, setSignUpError] = useState(''); const [signUpSuccess, setSignUpSuccess] = useState(false); const onChangePassword = useCallback( (e) => { setPassword(e.target.value); setMismatchError(e.target.value !== passwordCheck); }, [passwordCheck], ); const onChangePasswordCheck = useCallback( (e) => { setPasswordCheck(e.target.value); setMismatchError(e.target.value !== password); }, [password], ); const onSubmit = useCallback( (e) => { e.preventDefault(); if (!mismatchError && nickname) { console.log('서버로 회원가입하기'); setSignUpError(''); setSignUpSuccess(false); axios .post('/api/users', { email, nickname, password, }) .then((response) => { console.log(response); setSignUpSuccess(true); }) .catch((error) => { console.log(error.response); setSignUpError(error.response.data); }) .finally(() => {}); } }, [email, nickname, password, passwordCheck, mismatchError], ); if (data === undefined) { return <div>로딩중...</div>; } if (data) { return <Navigate replace to="/workspace/sleact/channel/일반" />; } return ( <div id="container"> <Header>Sleact</Header> <Form onSubmit={onSubmit}> <Label id="email-label"> <span>이메일 주소</span> <div> <Input type="email" id="email" name="email" value={email} onChange={onChangeEmail} /> </div> </Label> <Label id="nickname-label"> <span>닉네임</span> <div> <Input type="text" id="nickname" name="nickname" value={nickname} onChange={onChangeNickname} /> </div> </Label> <Label id="password-label"> <span>비밀번호</span> <div> <Input type="password" id="password" name="password" value={password} onChange={onChangePassword} /> </div> </Label> <Label id="password-check-label"> <span>비밀번호 확인</span> <div> <Input type="password" id="password-check" name="password-check" value={passwordCheck} onChange={onChangePasswordCheck} /> </div> {mismatchError && <Error>비밀번호가 일치하지 않습니다.</Error>} {!nickname && <Error>닉네임을 입력해주세요.</Error>} {signUpError && <Error>{signUpError}</Error>} {signUpSuccess && <Success>회원가입되었습니다! 로그인해주세요.</Success>} </Label> <Button type="submit">회원가입</Button> </Form> <LinkContainer> 이미 회원이신가요? <Link to="/login">로그인 하러가기</Link> </LinkContainer> </div> ); }; export default SignUp; 강의 내용을 그대로 따라쳤으며, mutate를 사용했습니다. 그리고 redirect 대신 navigate를 사용했습니다.. 아무리 봐도 어디서 오류가 나는건지 모르겠습니다,, fetcher 코드//로그인 받은 후 어떻게 처리할건지 //백엔드 서버주소와 프론트 서버주소가 다르면 쿠키를 보낼수도 받을 수도 없음 //이때 설정하는게 withCredentials임 import axios from 'axios'; const fetcher = (url: string) => axios .get(url, { withCredentials: true, }) .then((response) => response.data); export default fetcher; 파일구조
- 해결됨Slack 클론 코딩[실시간 채팅 with React]
webpack 관련 질문
안녕하세요 제로초님, cra없이 개발환경 설정하는 부분에서 궁금한 것이 있어 질문 드립니다.webpack.config를 ts로 하는 이유?webpack.config.js 이렇게 쓰면 require를 써야하긴 하지만 이렇게 하면 굳이 ts-node를 설치하지 않아도 되는 것 같아서 js로 하는게 더 좋아 보이는데 ts로 하는 이유가 있나요 hmr 플러그인 사용webpack5기준으로 webpack-dev-server에 hot: true, liveReload:true 설정을 하면 hotModuleReplacementPlugin, 이랑 ReactRefresh 플러그인이 필요없는 게 아닌가요?추가적인 플러그인을 설치하는 것이 더 비효율적인 게 아닌지 제가 잘못알고 있는 것이라면 알려주시면 감사하겠습니다. 웹팩 최적화?당연히 웹팩 강좌가 아니기 때문에 불필요 하겠지만 공부하다 보니 terser, minifyPlugin 등등 새로운게 계속 나오네요.. 웹팩 성능을 최적화 하기위한 방법 인 것 같은데 실무에서는 이런 플러그인들을 사용하게 되나요 아니면 강좌에 나온 설정들로 실무에서도 개발이 진행되는지 궁금합니다.. 웹팩 이외의 빌드툴?이건 강좌도 아니고 웹팩 관련 질문도 아니라 죄송한데 넘 궁금해서 양해를 구해봅니다.. 공부하다보니 astro, vite 등등 웹팩 보다 성능이 좋은 빌드 툴이 있는 것 같은데 실무에서 도입할 만큼 안정적인지 궁금하네요, 추가로 webpack에서 babel-loader 대신 esbuild-loader 썼을 때도 단점이 있을 까요?