39,600원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
useMutation 으로 onError 서버에서 받은 403 message 출력
안녕하세요 제로초님 제가 react-query 를 사용하고 있습니다. Slack 에서 받은 403 메시지 (이미 사용 중인 이메일, 이미 사용 중인 아이디 입니다.) 이것을 프론트 단에서 출력 해 주고 싶습니다. const { mutate } = useMutation(Sign, { onSuccess: (data, context) => { console.log(data); }, onError: (data, context, error) => { // LoginErrorMessage(err); console.log(error); }, }); react-query useMutation Hook 를 사용했습니다. onError 으로 요청 실패하면 - Network Preview 에 왜 실패 했는 지 (사용 중인 아이디 입니다.) 문구가 나오고. 문제는 에러 메시지를 받은 error 부분에 console.log(error)로 콘솔에 출력 해 보았는데, 콘솔에 빈 값으로 출력됩니다. 어떻게 해야 할까요. error 가 error.message 가 아닌가? 해서 data 도 넣어봤는 데 - onError 가 작동하면 콘솔에 빈 값으로 나옵니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
코드스플릿팅
코드스플릿팅이란 기능은 react에서도 셋팅만해주면 사용가능한것인가요 ? 아니면 next.js 에서만 사용가능한가요 ? 아그리고 next강의할때 swr 보여주셧는데 swr도 그냥react에서도 사용하나요 ?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
TypeError (0 , swr__WEBPACK_IMPORTED_MODULE_5__.useSWRConfig) is not a function 에러와 관련하여 질문 드립니다.
안녕하세요. 강의를 듣다가 에러가 발생하여 질문드립니다. 타입스크립트를 다루는데 익숙하지 않아 에러가 어디서 발생했는지 가늠이 잘 안됩니다ㅜㅜ 공지사항에 올려주신 대로 ser-devtools 업데이트에 맞게 client.tsx를 아래와 같이 수정했는데 다음과 같은 에러메세지가 계속 나와 화면이 보이지 않습니다 코드를 수정할 때 core-js와 @jjordy/swr-devtools 를 함께 설치해주었습니다. 이때 @jjordy/swr-devtools 를 설치할 때 에러가 나 -f로 강제로 설치했었는데 혹시 이것이 문제가 되는 것일까요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
혹시 db비밀번호
react nodebird 만들기에서 .env에 비밀번호1234 사용햇는데 여기서 create할때 비번입력할때 1234입력하면 게속 error 1045 (28000): access denied for user 'root'@'localhost' (using password: no) 이렇게뜨네요.. 찾아보니까 mysql 비밀번호가안맞아서 그렇다는데..하...초기화하고 싶어서 유형 2) ERROR 1045 (28000): Access denied for user 'root@'localhost' (using password: YES) - 사용자의 비밀번호가 틀렸을 경우 나타나는 오류 문구, 아래 해결 방법에 나와있는 명령어들을 입력. [해결 방법] mysql > use mysqlmysql > update user set password=password('비밀번호') where user='사용자'; // 비밀번호 변경mysql > flush privileges; // 변경사항 적용 C:\Program Files\MySQL\MySQL Server 8.0\bin입니다. 설치경로에서 mysql 접속하라길래 해당경로에서 접속하려는데 실행할수없는 명령어라고하고 기존에 노드버드연결했던 커넥트 삭제햇다가 다시연결하려고 1234입력하니까 이번엔또 같은에러뜨고.. 어찌해야할까요
- 미해결Slack 클론 코딩[실시간 채팅 with React]
Cannot read properties of undefined (reading 'map') 관련 질문드립니다.
안녕하세요. Cannot read properties of undefined (reading 'map') 하여 질문을 드리고자합니다. 문제는 해결 했으나 타입스크립트가 미숙하여 이런 현상이 발생한거 같은데 왜 이런 문제가 발생했는지 잘 몰라서 질문 드립니다. 오류가 발생 된 flow는 login 시 /workspace/sleact/channel/일반 으로 접을 했을 경우 그림과 같은 에러가 발생하였습니다. map 관련해서 초기 값이 안들어오는 부분의 에러인것을 확인하고 userData 옵셔널 체이닝을 에 해당 하는 값에 앞부분만 주었는데 에러가 발생한거 같습니다. 자세한 코드는 이렇습니다. <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> 여기서 Workspaces에도 옵셔널 체이닝을 걸어줘야 에러가 풀렸는데요. userData?Workspaces?.map... 제로초님의 코딩을 전부 클론하여 작성하였는데 왜 이런 문제가 발생했는지 이해를 못하고 있습니다. 혹여 typescript에서는 전부 옵셔널 체이닝을 적용해줘야하나요 ... ?? 세부적인 코드는 아래 첨부드립니다. 아 그리고 userData는 정상적으로 다 받아옵니다. import fetcher from '@utils/fetcher'; import axios from 'axios'; import { type } from 'os'; import React, { useCallback, ReactNode, useState } from 'react'; import { Redirect, Route, Switch, useParams } from 'react-router'; import useSWR from 'swr'; import { Header, RightMenu, ProfileImg, WorkspaceWrapper, Workspaces, Channels, Chats, WorkspaceName, MenuScroll, ProfileModal, LogOutButton, WorkspaceButton, AddButton, WorkspaceModal, } from '@layouts/Workspace/styles'; import gravatar from 'gravatar'; import Channel from '@pages/Channel'; import DirectMessage from '@pages/DirectMessage'; 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'; type Props = { children?: ReactNode; }; function Workspace({ children }: Props) { const [showUserMenu, setShowUserMenu] = useState(false); const [showCreateWorkspaceModal, setShowCreateWorkspaceModal] = useState(false); const [showWorkSpaceModal, setShowWorkSpaceModal] = useState(false); const [showCreateChannelModal, setShowCreateChannelModal] = useState(false); const [newWorkSpace, onChangeNewSpace, setNewWorkSpace] = useInput(''); const [newUrl, onChangeNewUrl, setNewUrl] = useInput(''); const { workspace } = useParams<{ workspace: string }>(); const { data: userData, error, revalidate, mutate, } = useSWR<IUser | false>('http://localhost:3095/api/users', fetcher); const { data: channelData } = useSWR<IChannel[]>( userData ? `http://localhost:3095/api/workspaces/${workspace}/channels` : null, fetcher, ); const onLogout = useCallback(() => { axios .post('http://localhost:3095/api/users/logout', null, { withCredentials: true, }) .then(() => { // revalidate(); mutate(false, false); }); }, []); const onClickUserProfile = useCallback((e) => { e.stopPropagation(); 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(() => { revalidate(); setShowCreateWorkspaceModal(false); setNewWorkSpace(''); setNewUrl(''); }) .catch((error) => { console.dir(error); toast.error(error.response?.data, { position: 'bottom-center' }); }); }, [newWorkSpace, newUrl], ); const onCloseModal = useCallback(() => { setShowCreateWorkspaceModal(false); setShowCreateChannelModal(false); }, []); const toggleWorkSpaceModal = useCallback(() => { setShowWorkSpaceModal((prev) => !prev); }, []); const onClickAddChannel = useCallback(() => { setShowCreateChannelModal(true); }, []); if (!userData) { return <Redirect to="/login" />; } return ( <div> <Header> <RightMenu> {/* 우측 상단 프로필 active */} <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={onClickUserProfile}> <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/${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> <button onClick={onClickAddChannel}>채널만들기</button> <button onClick={onLogout}>로그아웃</button> </WorkspaceModal> </Menu> {channelData?.map((v) => ( <div>{v.name}</div> ))} </MenuScroll> </Channels> <Chats> <Switch> <Route path="/workspace/:workspace/channel/:channel" component={Channel} /> <Route path="/workspace/:workspace/dm/:id" component={DirectMessage} /> </Switch> </Chats> </WorkspaceWrapper> {/* 워크 스페이스 생성 모달 onClickCreateWorkspace */} <Modal show={showCreateWorkspaceModal} onCloseModal={onCloseModal}> <form onSubmit={onCreateWorkspace}> <Label id="workspace-label"> <span>워크스페이스 이름</span> <Input id="workspace" value={newWorkSpace} onChange={onChangeNewSpace}></Input> </Label> <Label id="workspace-label"> <span>워크스페이스 url</span> <Input id="workspace" value={newUrl} onChange={onChangeNewUrl}></Input> </Label> <Button type="submit">생성하기</Button> </form> </Modal> {/* 채널 만들기 모달 onClickAddChannel*/} <CreateChannelModal show={showCreateChannelModal} onCloseModal={onCloseModal} setShowCreateChannelModal={setShowCreateChannelModal} /> </div> ); } export default Workspace;
- 미해결Slack 클론 코딩[실시간 채팅 with React]
(공유) swr 1버전 mutate 사용 시
login.tsx 의 onSubmit 에서 swr 사용 시, swr1버전을 사용하면 revalidate() 는 더이상 없고, mutate() 를 사용해야 합니다. 강의에서 사용한 0.5버전의 revalidate() 같이 아무 인자 없이 mutate()로 호출하면 data가 계속 undefined 상태입니다. ㅠㅠ 그래서 구글링을 통해 해결한 결과 공유합니다!! axios then 을 통해 받는 response 의 데이터를 mutate 인자로 넣어주시면 됩니다. 참고한 사이트 올립니다! https://steadily-worked.tistory.com/565
- 미해결Slack 클론 코딩[실시간 채팅 with React]
childern 질문드립니다.
안녕하세요, childern 사용 시 해당에러가 떠서 문의드립니다. FC를 선언했음에도 불구하고 에러가뜨네요..ㅠ
- 미해결Slack 클론 코딩[실시간 채팅 with React]
SWR 사용 시 에러가 발생하여 데이터가 전달되지 않습니다.
안녕하세요. 강의에서 배운 SWR을 토대로 프로젝트를 진행중에 궁금한 점이 생겨 질문드립니다. 1. DetailTerm.tsx import React from 'react' import useSWR from 'swr' import fetcher from 'utils/fetcher' const DetailTerm = () => { const { data } = useSWR( 'https://jsonplaceholder.typicode.com/users/1', fetcher, ) if (data === undefined) return <div>No Data!</div> return ( <ul> {data.map((term) => ( <li key={term.id}> {term.name} ({term.description}) </li> ))} </ul> ) } export default DetailTerm 2. fetcher.ts import axios from 'axios' const fetcher = (url: string) => { axios .get(url, { withCredentials: true, }) .then((response) => response.data) } // `${process.env.NEXT_PUBLIC_API_URL}/url/` export default fetcher 이와 같이 코드를 작성하였으나 data.map에서 Property 'map' does not exist on type 'void'.ts(2339) 에러가 발생하였고, No Data!가 떠 결국 data에 response.data가 저장되지 않고 있다는 사실을 알게 되었습니다. 이에 useSWR에 타입을 부여하여 해결할 수 있다는 사실을 구글링을 통해 알게되었고, useSWR<string[]>~ 와 같이 제너릭을 넣어서 작성했더니 URL에 위와 같은 에러 밑줄이 발생하였고 여전히 No Data!가 리턴되고 있습니다. 해결책이 뭔지 궁금합니다.ㅜㅜ 아 그리고, 선생님의 슬리액 깃허브를 확인해봤는데 SWR 대신 React-Query를 적용하신 프로젝트 파일도 있더라구요. 혹시 리액트 쿼리로는 따로 강의 진행 계획은 없으신건지도 궁금합니다. 감사합니다.
- 해결됨Slack 클론 코딩[실시간 채팅 with React]
웹소켓 관련 개념질문입니다.
안녕하세요. 현재 "DM채팅하기" 강의를 진행하기 전에도 시크릿모드로 두개를 띄워서 해보면 지금도 채팅 통신이 가능한데 이상태에선 계속 서버로 보냈다가 다시 가져오고 그런 상태이기 때문에 소켓통신으로 바꾸려고 socket.on 을 추가하는 것인가요? 그러니까 useEffect(()=>socket?.on('dm', onMessage) 이부분을 하는 이유가 http프로토콜을 소켓 프로토콜로 바꾸는 개념인가요? 동작 방식이 좀 헷갈리네요.. 제가 이해한 부분이 맞을까요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
멘션기능 버그
우연히 찾아낸 버그입니다 닉네임을 nickname]) K 라고 지으면 멘션기능을 쓰면 닉네임만 남지 않고 예시형태에서 커서가 K뒤에서 깜빡입니다(다른 닉네임들은 정상작동합니다.) | : 커서입니다. 예시) @[nickname]) |K](3) 다른 잘되는 예시) nickname2 J | 어느 부분에서 문제가 생긴 걸까요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
(공유) 리액트 v6부터는 route가 바뀌어서 중첩라우터 이렇게 적용하셔야합니다
App 파트에서 workspace다음 와일드카드/* 를 표시해주셔야 합니다 workspace 안에서는 path에 /workspace/:workspace를 쓰면 App에서 workspace/:workspace/* 가 있어서 두개가 중복되기때문에 바로 /channel/:channel 로 path를 작성하시면 됩니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
npm run dev시 오류
Property 'revalidate' does not exist on type 'SWRResponse 오류가 떠서 npm i swr을 해봤는데 안되네요 "swr": "^1.1.2", Cannot find module 'react-custom-scrollbars' or its corresponding type declarations pakage.json을 보면 react-custom-scrollbars-2가 있는데 scrollbars와 scrollbars-2가 달라서 그런건가요? "react-custom-scrollbars-2": "^4.3.0", Cannot find namespace 'SocketIOClient'. "socket.io-client": "^4.4.1", socket io client도 깔려 있는데 왜 찾을수 없다고 뜨는지 모르겠습니다. Parameter 'index' implicitly has an 'any' type. 그 밖에 알수 없는 이런 오류도 뜹니다
- 미해결Slack 클론 코딩[실시간 채팅 with React]
2번 클릭해야 axios.delete 반응하는 이유
안녕하세요 제로초님 제가 출력 된 것들을 삭제 하는 함수를 만들고 있습니다. 제가 category 값을 map 함수로 출력 해 준 뒤 li value값에 category id 값을 넣어 주었습니다. 값을 제어 할려고 -> useState(categoryId) 만들어 주었습니다. li 태그 onClick 를 하면 ContentDelete 라는 삭제하는 함수가 호출 됩니다. 그리고 e.target.value 을 이용해서 -> categoryId 에 value 값을 넣어주었습니다. 그리고 categoryId 값을 서버에 호출 해 주었습니다. 하지만 Delete li 태그 onClick 를 2번 해야 axios.delete 가 먹힙니다. DELETE http://localhost:3065/api/category/0 404 (Not Found) -> 한 번 클릭 할 경우 useState 초기값인 0이 나옵니다. DELETE http://localhost:3065/api/category/18 404 (Not Found) -> 한 번 더 클릭해야 18이 나오고 404 에러가 나지만 삭제는 됩니다. 왜 이러는 지 모르겠습니다... async await 으로 비동기 처리도 제대로 해 주고 return 문에다가 넣어주었는데 왜 처음 클릭하면 state 초기값인 0이 찍히는 지... const [categoryId, setCategoryId] = useState<number>(0); const ContentDelete = useCallback( async (e) => { setCategoryId(e.target.value); return await axios.delete(`http://localhost:3065/api/category/${categoryId}`, { withCredentials: true, }) }, [categoryId]) return ( <ul> {categorys && categorys.map((test: { id: any, content: React.ReactChild }) => ( <li key={test.id} value={test.id} onClick={ContentDelete}>{test.content} <AiOutlineClose /> </li> ))} </ul> )
- 미해결Slack 클론 코딩[실시간 채팅 with React]
127.0.0.1:8888으로 뜨는 이유
안녕하세요, 환경 세팅은 잘 되었는데, npm run dev를 하면 3090으로 가는게 아니라 8888으로 가게 되어 의문이 들어 질문을 남기게 되었습니다. 혹시 왜 그런건가요??
- 미해결Slack 클론 코딩[실시간 채팅 with React]
맥에서 npm install 에러 command sh -c node-gyp rebuild 해결
안녕하세요, 혹시 저처럼 안되는 분들을 위해.. 맥에서, `back` 폴더에서 npm i 할 때 node-gyp rebuild 에러가 나는 경우에, 저는 `npm install -g npm@6` 로 npm 버전을 다운그레이드 해서 해결했습니다. 제 기존 버전은 8.3.0 이었습니다. 화이팅입니다.
- 미해결Slack 클론 코딩[실시간 채팅 with React]
로그인후 성공후에 swr로 요청한 user가 호출되지 않습니다
안녕하세요! swr 사용하기(쿠키공유하기) 강좌를 보던중에 코드를 제로초님과 분명 똑같이 작성했는데 저는 로그인 성공후에 swr에서 user를 확인하는 get 방식 api가 호출되지 않습니다 ㅠㅠ 로그인 화면 접속시에는 강좌와 같이 호출이 되는데말이죠.. 몇일 고민하다 글올립니다ㅠㅠ 무엇이 문제인가요 네트워크 요청 Login/index.tsx 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 } from "react-router-dom"; import useSWR from "swr"; const LogIn = () => { const { data, error } = useSWR("http://localhost:3095/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("http://localhost:3095/api/users/login", { email, password }) .then(() => {}) .catch((error) => { setLogInError(error.response?.data?.statusCode === 401); }); }, [email, password], ); 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; fetcher.ts import axios from "axios"; const fetcher = (url: string) => axios.get(url).then((response) => response.data); export default fetcher;
- 미해결Slack 클론 코딩[실시간 채팅 with React]
react-router-dom 최신버전 쓰시는분들
최신버전 react router dom 쓰시는 분들은 App.tsx의 Swich와 Redirect 가 바뀌어서 제 스크린샷 처럼 하면 잘 돌아갑니다~~ 저는 강의에 따라 버전낮춰서 다운받는거보다 최신버전에 맞춰서 강의에 쓰인 코드들을 계속 리팩토링하는 걸 좋아해서 이렇게 했습니다!
- 미해결Slack 클론 코딩[실시간 채팅 with React]
취약점이 왜 여러개 나올까요?
npm audit fix를 하라고 해서 하긴 했는데~ $ npm i -D @types/react-router @types/react-router-dom npm WARN sleact@1.0.0 No description npm WARN sleact@1.0.0 No repository field. npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.13 (node_modules\we bpack-dev-server\node_modules\fsevents): npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@ 1.2.13: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64" }) npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.3.2 (node_modules\fse vents): npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@ 2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"} ) + @types/react-router@5.1.18 + @types/react-router-dom@5.3.3 updated 2 packages and audited 838 packages in 6.837s 63 packages are looking for funding run `npm fund` for details found 8 vulnerabilities (2 low, 5 moderate, 1 high) run `npm audit fix` to fix them, or `npm audit` for details
- 미해결Slack 클론 코딩[실시간 채팅 with React]
devServer 오류
강의대로 다 따라하고 나서 명령 프롬프트에 npm run dev 할 때 이 devServer 때문에 막힙니다 어떻게 해결해야 하나요?
- 미해결Slack 클론 코딩[실시간 채팅 with React]
상태관리 질문드립니다.
안녕하세요, 강의 내용과는 관련이 적은 내용일수도 있는데 궁금해서 질문 남깁니다. 만약에 비동기 처리를 위한 상태 관리 thunk, saga 등을 사용한다고 하면, 어떤 데이터까지 상태로 저장해야되는지 궁금합니다. 제 생각에는 로그인한 유저는 전역에서 관리를 해야하니까 상태로 저장하는 것이 이해가 되는데요, 만약 채팅에 대한 정보를 상태로 저장할 때는 어떤 데이터가 저장되어야 하는지 감이 잡히지 않습니다. 사용자가 업데이트된 채팅을 보기 위해서는 채팅 목록, 메시지 목록을 모두 상태로 저장하고 있는 것이 맞을까요...?