묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Slack 클론 코딩[실시간 채팅 with React]
라이브러리 질문
이렇게 코드 옆에 설명 나오게 하고싶은데 어떻게 해야될까요?
-
미해결Slack 클론 코딩[실시간 채팅 with React]
DM 전송 테스트시 500 에러
안녕하세요. 해당 강의에서 제가 DM을 확인하려고 전송을 해봤는데 500에러가 나오지만, 다시 데이터를 불러오면 DM 자체는 서버로 잘 간거처럼 나와서요. payload도 정상적으로 보내진거 같은데, 혹시 제가 api 추상화쪽을 잘못했나 싶지만,, 그런거 같지는 않아서 고민하다가 이렇게 질문을 올려보아요 ! 이게 해당 handleSubmut 코드와 API 추상화 코드입니다. 한번 봐주시면 감사하겠습니다.
-
미해결Slack 클론 코딩[실시간 채팅 with React]
onDragLeave 이벤트
onDragOver를 통해서 화면 안으로 파일을 넣으면 업로드라는 문자가 뜨는데 다시 파일을 밖으로 꺼내도 업로드! 라는 화면이 계속 떠 있더라구요 그래서 onDragLeave를 사용하여 dragover를 false로 만들어줘서 해결하긴 했는데, onDragOver만 사용했을 때는 업로드! 화면이 깜빡이지 않았는데, onDragLeave를 같이 사용하니까 업로드! 화면이 마우스를 움직일 때마다 깜빡이면서 채팅창 부분이 리렌더링 되는데, 더 효율적인 방법이 있을까요??const DM = () => { const [dragOver, setDragOver] = useState(false); const onDragOver = useCallback((e: any) => { e.preventDefault(); setDragOver(true); }, []); const onDragLeave = useCallback((e: any) => { e.preventDefault(); setDragOver(false); }, []); if (!userData || !myData) { return null; } // useSWRInfinite가 2차원 배열이기 때문에 1차원 배열로 만들어서 reverse를 해준다. const chatSections = makeSection(chatData ? [...chatData].flat().reverse() : []); return ( <Container onDrop={onDrop} onDragOver={onDragOver} onDragLeave={onDragLeave}> <Header> <img src={gravatar.url(userData.email, { s: '24px', d: 'retro' })} alt={userData.nickname} /> <span>{userData.nickname}</span> </Header> {/* chatData => 채팅을 DM에 표시해주기 위함 */} <ChatList chatSections={chatSections} ref={scrollbarRef} isEmpty={isEmpty} isReachingEnd={isReachingEnd} setSize={setSize} /> <ChatBox chat={chat} onChangeChat={onChangeChat} onSubmitForm={onSubmitForm} /> {dragOVer && <DragOVer>업로드!</DragOVer>} </Container> ); }; export default DM;코드는 해당되는 부분만 적었습니다.
-
미해결Slack 클론 코딩[실시간 채팅 with React]
프론트엔드 세팅하기
안녕하세요~강의 초기 세팅에서 제로초님 git에서 clone하여 내려받아 백엔드 DB연결까지 했습니다. 그런데 프론트엔드 세팅하기 강좌에서는 어떻게 진행해야할지 모르겠네요.. 이미 깃에 완성된 모든 코드가 내려받아진상태라서 당황스러워서 질문글을 찾아보니..setting폴더의 ts부터 시작하라는 글을 보았는데요..저는 너무 많은 폴더가 중복되어있는게 복잡하고 정리가 안된 느낌이라서... 삭제하고싶은데요그럼 alecture 폴더와, front-js폴더, front-rq, fornt 폴더를 삭제 한 뒤 setting>ts 폴더만 상위로 꺼내와 alecture로 이름을 변경해서 작업을 진행하면 되나요?또 삭제해도 되는 폴더가 있으면 알려주세요..처음부터 시작하고싶지만 백엔드 부분때문에 안될꺼같아서 참 난감합니다..
-
미해결Slack 클론 코딩[실시간 채팅 with React]
workspace 값이 읽히지 않습니다.(prototype)
채널에서 워크스페이스 정보가 뜨지 않아 검사 창을 열어보니, 제 UserData 객체에 Workspace객체가 없고, prototype 객체가 하나 있는 것을 발견했습니다.이후 네트워크에서 값을 확인해보니, 서버는 user의 data에 정상적으로 워크스페이스를 보내고 있는 것도 확인했습니다.혹시나 싶어, 새 워크스페이스를 만들어서 보내도 서버에서는 정상적으로 처리해주었지만, 클라이언트에선 전혀 받지 못하고있습니다. 이럴 경우 어떻게 처리해주어야 하는지 여쭤보고싶습니다.워크스페이스 부분의 코드를 첨부하며 리액트(+dom)의 버전은 17, axios의 버전은 0.26.1, swr의 버전은 2.0.3 입니다. import Menu from '../../components/Menu'; import loadable from '@loadable/component'; import axios from 'axios'; import React, { useCallback, useState, VFC } from 'react'; import { Route, Switch } from 'react-router'; import useSWR from 'swr'; import fetcher from '../../utills/fetcher'; import { AddButton, Channels, Chats, Header, LogOutButton, MenuScroll, ProfileImg, ProfileModal, RightMenu, WorkspaceButton, WorkspaceModal, WorkspaceName, Workspaces, WorkspaceWrapper, } from '../Workspace/styles'; import { IUser } from '../../typings/db'; import { Link } from 'react-router-dom'; import Modal from '@components/Modal'; import { Button, Input, Label } from '@pages/Login/styles'; import useInput from '@hooks/useInput'; import { toast } from 'react-toastify'; const Channel = loadable(() => import('../../pages/Channel')); const DirectMessage = loadable(() => import('../../pages/DirectMessage')); const Workspace: VFC = () => { const { data: UserData, error, mutate, } = useSWR<IUser | false>('/api/users', fetcher, { dedupingInterval: 2000, }); const [showUserMenu, setShowUserMenu] = useState(false); const [showCreateWorkspaceModal, setShowCreateWorkspaceModal] = useState(false); const [newWorkspace, onChangeNewWorkspace, setNewWorkpsace] = useInput(''); const [newUrl, onChangeNewUrl, setNewUrl] = useInput(''); const onClickUserProfile = useCallback((e) => { e.stopPropagation(); setShowUserMenu((prev) => !prev); }, []); const onClickCreateWorkspace = useCallback(() => { setShowCreateWorkspaceModal(true); }, []); console.log('showUserMenu3 :', showUserMenu); console.log('UserData LastCheck: ', UserData); const onLogout = useCallback(() => { axios .post('http://localhost:3095/api/users/logout', null, { withCredentials: true, }) .then((response) => { mutate(response.data, false); console.log('onLogOut :', response.data); }) .catch((error) => { alert(error.response.data ? error.response.data : '애러 캐치 실패'); }); }, []); const onCloseModal = useCallback(() => { setShowCreateWorkspaceModal(false); }, []); const onCreateWorkspace = useCallback( (e) => { e.preventDefault(); if (!newWorkspace || !newWorkspace.trim()) return; if (!newUrl || !newUrl.trim()) return; axios .post( '/api/workspaces', { workspace: newWorkspace, url: newUrl, }, { withCredentials: true, }, ) .then((response) => { console.log('modalData :', response); mutate(); setShowCreateWorkspaceModal(false); setNewWorkpsace(''); setNewUrl(''); console.log('data check CreateWS: ', UserData); }) .catch((error) => { console.dir(error); toast.error(error.response?.data, { position: 'bottom-center' }); }); }, [newWorkspace, newUrl], ); // if (!data) { // console.log('data check back to login: ', data); // return <Redirect to="/login" />; // } console.log(' :', UserData); return ( <div> <Header> <span onClick={onClickUserProfile}> <ProfileImg src="../../img/leaf_toy.png" alt="fail to load profile" /> {showUserMenu && ( <Menu style={{ right: 0, top: 38 }} show={showUserMenu} onCloseModal={onClickUserProfile}> <ProfileModal> <img src="../../img/leaf_toy.png" /> <div> <span id="profile-name">{UserData ? UserData.nickname : 'false'}</span> <span id="profile-active">Active</span> </div> </ProfileModal> <LogOutButton onClick={onLogout}>로그아웃</LogOutButton> </Menu> )} </span> </Header> <WorkspaceWrapper> <Workspaces> {UserData !== false && UserData?.Workspaces?.map((ws) => { return ( <Link key={ws.id} to={`/workspace/${ws.id}/channel/일반`}> <WorkspaceButton>{ws.name.slice(0, 1).toUpperCase()}</WorkspaceButton> </Link> ); })} <AddButton onClick={onClickCreateWorkspace}>+</AddButton> </Workspaces> <Channels> <WorkspaceName>Select</WorkspaceName> <MenuScroll>menuScroll</MenuScroll> </Channels> <Chats> <Switch> <Route path="/workspace/channel" component={Channel} /> <Route path="/workspace/dm" component={DirectMessage} /> </Switch> </Chats> </WorkspaceWrapper> <Modal show={showCreateWorkspaceModal} onCloseModal={onCloseModal}> <form onSubmit={onCreateWorkspace}> <Label id="workspace-label"> <span>workspace 이름</span> <Input id="workspace" value={newWorkspace} onChange={onChangeNewWorkspace}></Input> </Label> <Label id="workspace-url-label"> <span>workspace url</span> <Input id="workspace" value={newUrl} onChange={onChangeNewUrl}></Input> </Label> <Button type="submit">생성하기</Button> </form> </Modal> </div> ); }; export default Workspace; 또, fetcher의 문제일 수 있을 것 같아, fetcher의 코드 또한 올려봅니다. import axios from 'axios'; import React from 'react'; const fetcher = <Data,>(url: string): any => { axios.get<Data>(url, { withCredentials: true }).then((response) => response.data); }; export default fetcher;
-
미해결Slack 클론 코딩[실시간 채팅 with React]
dist 폴더 안에 js파일 네임이 달라요
6:00 에 dist 파일이제 파일이랑 구조가 다른데 따로 설정을 해주신건가요??
-
해결됨Slack 클론 코딩[실시간 채팅 with React]
useSWRInfinite import
import useSWR, { useSWRInfinite } from 'swr'; const { data: chatData, mutate: mutateChat, revalidate, setSize } = useSWRInfinite<IDM[]>( (index) => `/api/workspaces/${workspace}/dms/${id}/chats?perPage=20&page=${index + 1}`, fetcher, );이렇게 하면 useSWRInfinite 가 import가 안되는데,import useSWRInfinite from 'swr/infinite'이렇게 따로 import 해주어야 import가 제대로 되네요
-
해결됨Slack 클론 코딩[실시간 채팅 with React]
@types/loadable__component module not found error 도와주시면 감사하겠습니다.!
에러해결 시도현황 (하단에 npm list 첨부했습니다.)생각한 원인node_moduels\@loadable\component 의 경로로 탐색을 하는 상황인데 npm i @types/loadable__component 결과로 다운받은 모듈의 위치는 @types 디렉토리아래 존재하는 상황이므로 노드가 경로를 못찾나? 라고 생각했습니다.=> 첫 시도에러 상황: module not fount 상황으로 @types directory 를 찾지 못해 생긴 문제로 생각했습니다.에러 메시지vscode 상황브라우저 상황=> @types 를 추가해 경로를 고쳐봤습니다. 마우스를 올리면 이런식으로 나오는데, 브라우저에서는 module not found 가 나옵니다.브라우저 상황=> 두 번째 시도에러 추론.. webpack 문제?잘은 모르지만, webpack config에 @loadable 을 추가해봐도 해결이 안됩니다. => @loadable 이 없는 상황에도 에러가 납니다. => @loadbale resolve argument 로, (__dirname, '@types') 에러납니다. (import loadable from '@loadable/component' 상황입니다.) => @loadable resolve argument 로, (__dirname, '@types/loadable__component') 에러납니다. (import loadable from '@loadable/component' 상황입니다.) 두 번째 시도의 에러 첨부본 모두 vscode 를 재부팅후 npm run dev를 실행한 결과입니다. ref) => npm list
-
미해결Slack 클론 코딩[실시간 채팅 with React]
? 연산자
아래의 코드에서 다음과 같은 에러가 나타납니다 {member.id === userData?.id && <span> (나)</span>}"'false | IUser' 형식에 'id' 속성이 없습니다.'false' 형식에 'id' 속성이 없습니다.",userData?.id는 userData가 존재할때만 id property에 접근하고, userData가 false면 접근하지 않는 것으로 알고 있는데, 왜 저러한 오류가 나타나는지 의문입니다.이전에도 error.response?.data 같은 문법을 많이 썼는데, 왜 저게 오류인지 의문입니다.
-
미해결Slack 클론 코딩[실시간 채팅 with React]
mutate, revalidate
4: 48 mutate 대신 revalidate를 사용하셨는데revalidate가 지금은 사용하지 않는 단어라서 지금까지 mutate로 대체해서 사용했어요제로초님처럼 로그인 했을 때 에러가 나진 않는데 바꾸지 않아도 되나요??
-
미해결Slack 클론 코딩[실시간 채팅 with React]
socket 연결된 것 같은데 네트워크 창
소켓이 연결 되어 DM 메시지에 초록 동그라미도 보이는데,12:44 초에 네트워크 창 보시면?EIO 라고 시작되는 네트워크 네임이 없고, 프로토콜도 http/1.1 만 나오고 websocket이라는 단어가 뜨는 부분이 없어요이건 연결이 안된건가요....??? 5:35초 부터 네트워크에 빨간 에러도 안뜨고 저 네임이 안들어갔었습니다.소켓 연결이 된 것 같은데 안떠서 헷갈리네요
-
미해결Slack 클론 코딩[실시간 채팅 with React]
code-server 사용시 react-router-dom 오류
안녕하세요개발환경으로 code-server을 사용하고 있는데, 문제를 도대체 알 수 없어서 질문 올립니다 ㅜㅜㅜㅜㅜㅜcode-server은 실행시 도메인/proxy/port로 실행 주소가 설정됩니다.그래서 hot-reload 웹소켓 연결을 설정하기위해서 webpack.config.ts에client: { webSocketURL: 'ws://0.0.0.0:443/proxy/3090/ws', },를 추가했습니다.index.html에서 src = "./dist/app.js"로 했습니다다른 모든 코드는 setting/ts를 그대로 npm i 했습니다.(여기까지 사전 참고내용) 이전 강의에서 App.tsx에 <div>코드어쩌구저쩌구</div>는 잘 나타났습니다.이번 강의에서 react-router-dom을 도입해서 code-server에서 실행을 하게되면 브라우저에 아무런 화면이 나오지 않습니다. 그러나, code-server가 아닌 구름ide에서 같은 파일을 실행할 경우(서버 주소가 proxy가 아니게됨)정상적으로 동작합니다. react-router-dom을 사용했더니 화면이 안보이기때문에 react-router-dom 과 관련된 문제라고 추측합니다.code-server에서 실행주소뒤에 'proxy/3090'이 붙었을때 문제가 생겼기 때문에 code-server 관련 문제로 추측됩니다.code-server사용시 가져온 파일이랑 구름ide사용시 가져온 파일 비교해보면, code-server에서 파일2개를 못 가져온것 같은데, 이게 문제인거 같기도 합니다. 또한, App.tsx에서 react-router을 빼고 다음과 같이 입력후 실행해보았습니다.import React from 'react'; import loadable from '@loadable/component'; import { Switch, Route, Redirect } from 'react-router-dom'; import SignUp from '@pages/SignUp'; const App = () => { return <SignUp />; }; export default App; 가져온 파일 목록은 다음과 같고, 정상적으로 signup화면이 나타납니다.이번에는 loadable/component가 문제인가 싶어서, 이걸빼고 다음과 같이 App.tsx를 입력했습니다.import React from 'react'; import loadable from '@loadable/component'; import { Switch, Route, Redirect } from 'react-router-dom'; import LogIn from '@pages/LogIn'; import SignUp from '@pages/SignUp'; const App = () => { return ( <Switch> <Route path="/login" component={LogIn} /> <Route path="/signup" component={SignUp} /> </Switch> ); }; export default App;그랬더니 이전과 같이 화면에 아무것도 안나옵니다. 도대체 뭐가 문제인지를 모르겠네요 ㅜㅜㅜ
-
미해결Slack 클론 코딩[실시간 채팅 with React]
DMList - NavLink 태그
NavLink 사용시, onClick에서 에러가 발생합니다(property) onClick?: React.MouseEventHandler<HTMLAnchorElement> | undefinedType 'void' is not assignable to type 'MouseEventHandler<HTMLAnchorElement> | undefined'.ts(2322)index.d.ts(1448, 9): The expected type comes from property 'onClick' which is declared here on type 'IntrinsicAttributes & NavLinkProps<unknown> & RefAttributes<HTMLAnchorElement>'이런 에러가 발생하였습니다.그래서 인터넷에 검색하니까onClick={()=>resetCount(member.id)}이렇게 바꿔주면 된다고 해서 바꿔줬더니 오류가 사라졌습니다.제로초님께서는 에러가 없었는데 저만 이 오류가 나타나는 이유가 뭘까요??// import EachDM from '@components/EachDM'; // import useSocket from '@hooks/useSocket'; import { CollapseButton } from '@components/DMList/styles'; import { IDM, IUser, IUserWithOnline } from '@typings/db'; import fetcher from '@utils/fetcher'; import React, { FC, useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router'; import { NavLink } from 'react-router-dom'; import useSWR from 'swr'; interface Props { userData?: IUser; } const DMList: FC<Props> = ({ userData }) => { const { workspace } = useParams<{ workspace?: string }>(); const { data: memberData } = useSWR<IUserWithOnline[]>( userData ? `/api/workspaces/${workspace}/members` : null, fetcher, ); // const [socket] = useSocket(workspace); const [channelCollapse, setChannelCollapse] = useState(false); const [countList, setCountList] = useState<{ [key: string]: number }>({}); const [onlineList, setOnlineList] = useState<number[]>([]); const toggleChannelCollapse = useCallback(() => { setChannelCollapse((prev) => !prev); }, []); const resetCount = useCallback((id) => { setCountList((list) => { return { ...list, [id]: 0, }; }); }, []); const onMessage = (data: IDM) => { console.log('dm왔다', data); setCountList((list) => { return { ...list, [data.SenderId]: list[data.SenderId] ? list[data.SenderId] + 1 : 1, }; }); }; useEffect(() => { console.log('DMList: workspace 바꼈다', workspace); setOnlineList([]); }, [workspace]); // useEffect(() => { // socket?.on('onlineList', (data: number[]) => { // setOnlineList(data); // }); // console.log('socket on dm', socket?.hasListeners('dm'), socket); // return () => { // console.log('socket off dm', socket?.hasListeners('dm')); // socket?.off('onlineList'); // }; // }, [socket]); return ( <> <h2> <CollapseButton collapse={channelCollapse} onClick={toggleChannelCollapse}> <i className="c-icon p-channel_sidebar__section_heading_expand p-channel_sidebar__section_heading_expand--show_more_feature c-icon--caret-right c-icon--inherit c-icon--inline" data-qa="channel-section-collapse" aria-hidden="true" /> </CollapseButton> <span>Direct Messages</span> </h2> <div> {!channelCollapse && memberData?.map((member) => { const isOnline = onlineList.includes(member.id); const count = countList[member.id] || 0; return ( <NavLink key={member.id} activeClassName="selected" to={`/workspace/${workspace}/dm/${member.id}`} onClick={resetCount(member.id)} > <i className={`c-icon p-channel_sidebar__presence_icon p-channel_sidebar__presence_icon--dim_enabled c-presence ${ isOnline ? 'c-presence--active c-icon--presence-online' : 'c-icon--presence-offline' }`} aria-hidden="true" data-qa="presence_indicator" data-qa-presence-self="false" data-qa-presence-active="false" data-qa-presence-dnd="false" /> <span className={count > 0 ? 'bold' : undefined}>{member.nickname}</span> {member.id === userData?.id && <span> (나)</span>} {count > 0 && <span className="count">{count}</span>} </NavLink> ); // return <EachDM key={member.id} member={member} isOnline={isOnline} />; })} </div> </> ); }; export default DMList; +)그리고 강의 코드와 깃헙에 있는 코드가 부분적으로 다른 부분이 있는데 븥여넣을 때마다 동영상을 정지시키고 코드를 다시 작성해야해서 조금 번거로운 부분이 있는 것 같아요ㅜㅜ
-
미해결Slack 클론 코딩[실시간 채팅 with React]
key prop 에러
Workspace에서 {channelData?.map((v) => ( <div>{v.name}</div> ))}콘솔에서 이 부분 때문에 key prop 에러가 나는 것 같은데 나중에 ChannelList로 바꿀꺼라서 신경 쓰지 않아도 되는 부분인가요??
-
미해결Slack 클론 코딩[실시간 채팅 with React]
'Date' 형식은 'ReactNode' 형식에 할당할 수 없습니다.
제로초님, 코드를 따라치는 중에'Date' 형식은 'ReactNode' 형식에 할당할 수 없습니다.라는 오류 메세지가 떴습니다. 그래서 index.d.ts에 들어가서type ReactNode에 Date 타입을 적어주었는데, 오류 메세지는 사라졌지만, 화면에 에러는 그대로 있습니다. 이 에러는 어떻게 해결하나요?? typescript는 v 18입니다!
-
해결됨Slack 클론 코딩[실시간 채팅 with React]
안녕하세요.
안녕하세요. 질문 있습니다.강의에서는 SWR을 쓰시는데 공부할때는 react-query를 쓰려고 합니다. 이유는 생태계가 더 큰 라이브러리를 사용할 목적이었는데 알려주신 npm trends에 검색해보니 둘 다 비슷비슷하더라고요.SWR도 아직 많이 쓰이는 추세인가요??? 궁금한 이유는 react-query 외에도 추가로 하나의 라이브러리를 동시에 더 써볼 생각이라 추천해주시면 감사합니다. 추가로 수업 외 질문으로 유틸리티 라이브러리?를 하나 써보려고 합니다. 기존에는 fxjs를 사용 중이었습니다. npm trends를 보니 가장 많이 사용하는 라이브러리는 lodash, rxjs인 것 같고 lamda도 많이 쓰는거 같고 타입스크립트로 된 fp-ts도 있는거 같은데 fp-ts를 제외한 것들 중 추천해주시는게 있을까요? 감사합니다!!
-
미해결Slack 클론 코딩[실시간 채팅 with React]
EachDM의 왼쪽에 있는 동그라미의 색깔이 초록색으로 변하지 않음
[제로초 강좌 질문 필독 사항입니다]질문에는 여러분에게 도움이 되는 질문과 도움이 되지 않는 질문이 있습니다.도움이 되는 질문을 하는 방법을 알려드립니다.https://www.youtube.com/watch?v=PUKOWrOuC0c0. 숫자 0부터 시작한 이유는 1보다 더 중요한 것이기 때문입니다. 에러가 났을 때 해결을 하는 게 중요한 게 아닙니다. 왜 여러분은 해결을 못 하고 저는 해결을 하는지, 어디서 힌트를 얻은 것이고 어떻게 해결한 건지 그걸 알아가셔야 합니다. 그렇지 못한 질문은 무의미한 질문입니다.1. 에러 메시지를 올리기 전에 반드시 스스로 번역을 해야 합니다. 번역기 요즘 잘 되어 있습니다. 에러 메시지가 에러 해결 단서의 90%를 차지합니다. 한글로 번역만 해도 대부분 풀립니다. 그냥 에러메시지를 올리고(심지어 안 올리는 분도 있습니다. 저는 독심술사가 아닙니다) 해결해달라고 하시면 아무런 도움이 안 됩니다.2. 에러 메시지를 잘라서 올리지 않아야 합니다. 입문자일수록 에러메시지에서 어떤 부분이 가장 중요한 부분인지 모르실 겁니다. 그러니 통째로 올리셔야 합니다.3. 코드도 같이 올려주세요. 다만 코드 전체를 다 올리거나, 깃헙 주소만 띡 던지지는 마세요. 여러분이 "가장" 의심스럽다고 생각하는 코드를 올려주세요.4. 이 강좌를 바탕으로 여러분이 응용을 해보다가 막히는 부분, 여러 개의 선택지 중에서 조언이 필요한 부분, 제 경험이 궁금한 부분에 대한 질문은 대환영입니다. 다만 여러분의 회사 일은 질문하지 마세요.5. 강좌 하나 끝날 때마다 남의 질문들을 읽어보세요. 여러분이 곧 만나게 될 에러들입니다.6. 위에 적은 내용을 명심하지 않으시면 백날 강좌를 봐도(제 강좌가 아니더라도) 실력이 늘지 않고 그냥 코딩쇼 관람 및 한컴타자연습을 한 셈이 될 겁니다.
-
미해결Slack 클론 코딩[실시간 채팅 with React]
DM List와 Channel List에서 CollapseButton을 누르면 CollapseButton의 왼편에 있는 화살표가 아래로 향해야 하는데 변하지 않습니다.
[제로초 강좌 질문 필독 사항입니다]질문에는 여러분에게 도움이 되는 질문과 도움이 되지 않는 질문이 있습니다.도움이 되는 질문을 하는 방법을 알려드립니다.https://www.youtube.com/watch?v=PUKOWrOuC0c0. 숫자 0부터 시작한 이유는 1보다 더 중요한 것이기 때문입니다. 에러가 났을 때 해결을 하는 게 중요한 게 아닙니다. 왜 여러분은 해결을 못 하고 저는 해결을 하는지, 어디서 힌트를 얻은 것이고 어떻게 해결한 건지 그걸 알아가셔야 합니다. 그렇지 못한 질문은 무의미한 질문입니다.1. 에러 메시지를 올리기 전에 반드시 스스로 번역을 해야 합니다. 번역기 요즘 잘 되어 있습니다. 에러 메시지가 에러 해결 단서의 90%를 차지합니다. 한글로 번역만 해도 대부분 풀립니다. 그냥 에러메시지를 올리고(심지어 안 올리는 분도 있습니다. 저는 독심술사가 아닙니다) 해결해달라고 하시면 아무런 도움이 안 됩니다.2. 에러 메시지를 잘라서 올리지 않아야 합니다. 입문자일수록 에러메시지에서 어떤 부분이 가장 중요한 부분인지 모르실 겁니다. 그러니 통째로 올리셔야 합니다.3. 코드도 같이 올려주세요. 다만 코드 전체를 다 올리거나, 깃헙 주소만 띡 던지지는 마세요. 여러분이 "가장" 의심스럽다고 생각하는 코드를 올려주세요.4. 이 강좌를 바탕으로 여러분이 응용을 해보다가 막히는 부분, 여러 개의 선택지 중에서 조언이 필요한 부분, 제 경험이 궁금한 부분에 대한 질문은 대환영입니다. 다만 여러분의 회사 일은 질문하지 마세요.5. 강좌 하나 끝날 때마다 남의 질문들을 읽어보세요. 여러분이 곧 만나게 될 에러들입니다.6. 위에 적은 내용을 명심하지 않으시면 백날 강좌를 봐도(제 강좌가 아니더라도) 실력이 늘지 않고 그냥 코딩쇼 관람 및 한컴타자연습을 한 셈이 될 겁니다.
-
미해결Slack 클론 코딩[실시간 채팅 with React]
react-router-dom v6 오류
다른분들의 질문들을 보면서 수정을 해보았지만 해결되지 않아 질문 올립니다. -react-router-dom 버전으로 인해 swtch는 Routes로 바꾸었으며 worspace뒤에 *을 붙였습니다.//App.ts <Routes> <Route path="/" element={<Navigate replace to="/login" />} /> <Route path="/login" element={<LogIn />} /> <Route path="/signup" element={<SignUp />} /> <Route path="/workspace/:workspace/*" element={<Workspace />} /> </Routes>workspace.tsx 에서는 밑에 코드로 바꾸었습니다. //Workspace.tsx <Routes> <Route path="channel/:channel" element={<Channel />} /> <Route path="dm/:id" element={<DirectMessage />} /> </Routes> 그리고 나서 login 은 잘 되고 쿠키도 잘 저장되지만 http://localhost:3090/workspace/sleact/channel/%EC%9D%BC%EB%B0%98 이 주소로 가면 아래 같은 에러가 뜨면서 창이 뜨지 않습니다. 어떤게 잘못된 걸까요 ㅠ TypeErrorCannot read properties of undefined (reading 'match')Call Stack useParams alecture/./node_modules/react-router/esm/react-router.js:760:34 ChannelList alecture/./components/ChannelList/index.tsx:40:75 renderWithHooks alecture/./node_modules/react-dom/cjs/react-dom.development.js:14985:18 mountIndeterminateComponent alecture/./node_modules/react-dom/cjs/react-dom.development.js:17811:13 beginWork alecture/./node_modules/react-dom/cjs/react-dom.development.js:19049:16 HTMLUnknownElement.callCallback alecture/./node_modules/react-dom/cjs/react-dom.development.js:3945:14 Object.invokeGuardedCallbackDev alecture/./node_modules/react-dom/cjs/react-dom.development.js:3994:16 invokeGuardedCallback alecture/./node_modules/react-dom/cjs/react-dom.development.js:4056:31 beginWork$1 alecture/./node_modules/react-dom/cjs/react-dom.development.js:23959:7 performUnitOfWork alecture/./node_modules/react-dom/cjs/react-dom.development.js:22771:12
-
해결됨Slack 클론 코딩[실시간 채팅 with React]
router.js:954 No routes matched location 에러가 발생합니다.
안녕하세요 제로초님, 현재 react-router-dom v6.6.2로 강의를 수강하고 있습니다. 앞부분에서 잘 따라하다가 어느 부분에서 잘못된 것인지 로그인을 했을 때 흰 화면과 함께 아래 첨부한 사진과 같은 에러가 발생하했습니다.API를 받아올 때 사용하는 params에 문제가 있나 싶었지만, http://localhost:3095/api/workspaces/sleact/channels 이 주소로 들어갔을 때 아래와 같은 데이터를 받아오는 것을 확인할 수 있었습니다.[ { "id": 1, "name": "일반", "private": false, "createdAt": "2023-01-26T08:07:33.000Z", "updatedAt": "2023-01-26T08:07:33.000Z", "WorkspaceId": 1, "Members": [ { "id": 2, "ChannelMembers": { "UserId": 2 } } ] } ] 데이터가 문제인가 싶어서 테이블도 삭제했다가 다시 만들어봤지만 해결할 수 없었습니다ㅠㅠ 어떻게 해결할 수 있을까요?? 혹시 몰라 코드는 모두 첨부하겠습니다! // App/index.tsx import React from 'react'; import loadable from '@loadable/component'; import { Routes, Route, Navigate } from 'react-router-dom'; const Login = loadable(() => import('@pages/Login')); const SignUp = loadable(() => import('@pages/SignUp')); const Workspace = loadable(() => import('@layouts/Workspace')); const App = () => { return ( <Routes> <Route path="/" element={<Navigate to="/login" />} /> <Route path="/login" element={<Login />} /> <Route path="/signup" element={<SignUp />} /> <Route path="/workspace/:workspace" element={<Workspace />} /> </Routes> ); }; export default App;// Workspace/index.tsx import React, { useCallback, useEffect, useState } from 'react'; import useSWR from 'swr'; import axios from 'axios'; import fetcher from '@utils/fetcher'; import gravatar from 'gravatar'; import { Navigate, Route, Routes } from 'react-router'; import { AddButton, Channels, Chats, Header, LogOutButton, MenuScroll, ProfileImg, ProfileModal, RightMenu, WorkspaceButton, WorkspaceModal, WorkspaceName, Workspaces, WorkspaceWrapper, } from './styles'; import loadable from '@loadable/component'; import Menu from '@components/Menu'; import { Link } from 'react-router-dom'; import { IChannel, IUser } from '@typings/db'; import Modal from '@components/Modal'; import { Button, Input, Label } from '@pages/SignUp/styles'; import useInput from '@hooks/useInput'; import { toast } from 'react-toastify'; import CreateChannelModal from '@components/CreateChannelModal'; import { useParams } from 'react-router'; const Channel = loadable(() => import('@pages/Channel')); const DirectMessage = loadable(() => import('@pages/DirectMessage')); 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 params = useParams<{ workspace?: string }>(); const { workspace } = params; const { data: userData, error, 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(() => { mutate(false, false); }); }, []); const onClickUserProfile = useCallback(() => { setShowUserMenu((prev) => !prev); }, []); const onCloseUserProfile = useCallback((e: React.MouseEvent) => { e.stopPropagation(); setShowUserMenu(false); }, []); const onClickCreateWorkspace = useCallback(() => { setShowCreateWorkspaceModal((prev) => !prev); }, []); const onCreateWorkspace = useCallback( (e: React.FormEvent) => { 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); 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 onClickAddChannel = useCallback(() => { setShowCreateChannelModal(true); }, []); const toggleWorkspaceModal = useCallback(() => { setShowWorkspaceModal((prev) => !prev); }, []); 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.email} /> <Menu style={{ right: 0, top: 38 }} show={showUserMenu} onCloseModal={onCloseUserProfile}> <ProfileModal> <img src={gravatar.url(userData.email, { s: '36px', d: 'retro' })} alt={userData.email} /> <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={`${ws.url}/channel/일반`}> <WorkspaceButton>{ws.name.slice(0, 1).toUpperCase()}</WorkspaceButton> </Link> ); })} <AddButton onClick={onClickCreateWorkspace}>+</AddButton>; </Workspaces> <Channels> <WorkspaceName onClick={toggleWorkspaceModal}>Sleact</WorkspaceName> <MenuScroll> <Menu style={{ top: 95, left: 80 }} show={showWorkspaceModal} onCloseModal={toggleWorkspaceModal}> <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="/: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-name"> <span>워크스페이스 이름</span> <Input id="workspace" value={newWorkspace} onChange={onChangeNewWorkspace} /> </Label> <Label id="workspace-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; // CreateChannel/index.tsx import 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 } from 'react'; import { useParams } from 'react-router'; import { toast } from 'react-toastify'; import useSWR from 'swr'; interface Props { show: boolean; onCloseModal: () => void; setShowCreateChannelModal: (flag: boolean) => void; } const CreateChannelModal: React.FC<Props> = ({ show, onCloseModal, setShowCreateChannelModal }) => { const [newChannel, onChangeNewChannel, setNewChannel] = useInput(''); const params = useParams<{ workspace?: string }>(); const { workspace } = params; const { data: userData, error, mutate } = useSWR<IUser | false>('http://localhost:3095/api/users', fetcher); const { mutate: mutateChannel } = useSWR<IChannel[]>( userData ? `http://localhost:3095/api/workspaces/${workspace}/channels` : null, fetcher, ); const onCreateChannel = useCallback( (e: React.FormEvent) => { e.preventDefault(); axios .post( `http://localhost:3095/api/workspaces/${workspace}/channels`, { name: newChannel, }, { withCredentials: true }, ) .then(() => { setShowCreateChannelModal(false); mutateChannel(); setNewChannel(''); }) .catch((error) => { console.dir(error); toast.error(error.response?.data, { position: 'bottom-center' }); }); }, [newChannel, workspace], ); return ( <Modal show={show} onCloseModal={onCloseModal}> <form onSubmit={onCreateChannel}> <Label id="channel-label"> <span>채널 이름</span> <Input id="workspace" value={newChannel} onChange={onChangeNewChannel} /> </Label> <Button type="submit">생성하기</Button> </form> </Modal> ); }; export default CreateChannelModal;