작성
·
358
0
안녕하세요 제로초님. 강의 잘 듣고 있습니다.
다름이 아니라 채널 토글에서 각 채널을 선택하면 정상적으로 이동하지만, DMlist에서 선택하면 아래와 같이 useEffect가 무한루프처럼 호출되어 문제가 발생하는 것 같습니다.
제로초님께서 업로드해주신 코드와 동일하게 def에 workspace를 넣어 작성했는데, 어떠한 부분에서 위와 같이 무한로딩되는 에러가 발생하는지 못 찾겠습니다ㅠㅠ
DMList/index.tsx
// import useSocket from '@hooks/useSocket';
import { CollapseButton } from '@components/DMList/style';
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';
const DMList: FC = () => {
const { workspace } = useParams<{ workspace?: string }>();
const { data: userData, error, mutate } = useSWR<IUser>('/api/users', fetcher, {
dedupingInterval: 2000, // 2초
});
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([]);
setCountList({});
}, [workspace]);
// useEffect(() => {
// socket?.on('onlineList', (data: number[]) => {
// setOnlineList(data);
// });
// socket?.on('dm', onMessage);
// console.log('socket on dm', socket?.hasListeners('dm'), socket);
// return () => {
// socket?.off('dm', onMessage);
// 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 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);
return (
<NavLink
key={member.id}
activeClassName="selected"
to={`/workspace/${workspace}/dm/${member.id}`}
>
<i
className={`c-icon p-channel_sidebar__presence_icon p-channel_sidebar__presence_icon--dim_enabled p-channel_sidebar__presence_icon--on-avatar 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>{member.nickname}</span>
{member.id === userData?.id && <span> (나)</span>}
</NavLink>
);
})}
</div>
</>
);
};
export default DMList;
답변 1
0
useEffect를 주석 처리하고 실행하면 ...dm/3으로 이동한 후 화면이 멈춥니다. useEffect가 없어도 같은 문제가 발생하는 것 같습니다. DMList 컴포넌트에서 문제가 발생하는 것은 확실한 것 같은데, workspace/index와 DMList/index 외에는 DMList 컴포넌트를 사용하는 파일이 없습니다ㅠㅠ ChannelList는 문제없이 작동합니다.
workspace/index 코드는 아래와 같습니다.
import Menu from "@components/Menu";
import Modal from "@components/Modal";
import CreateChannelModal from "@components/CreateChannelModal";
import InviteWorkspaceModal from "@components/InviteWorkspaceModal";
import InviteChannelModal from "@components/InviteChannelModal";
import DMList from "@components/DMList";
import ChannelList from "@components/ChannelList";
import useInput from "@hooks/useinput";
import fetcher from "@utils/fetcher";
import axios from "axios";
import React, { FunctionComponent, useCallback, useState } from "react"
import { Link, Redirect, Route, Switch, useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import useSWR from "swr";
import {
AddButton,
Channels,
Chats,
Header,
LogOutButton,
MenuScroll,
ProfileImg,
ProfileModal,
RightMenu,
WorkspaceButton,
WorkspaceModal,
WorkspaceName,
WorkspaceWrapper,
Workspaces
} from "@layouts/Workspace/style";
import gravatar from 'gravatar';
import { Button, Input, Label } from '@pages/SignUp/style';
import { IChannel, IUser } from "@typings/db";
import loadable from "@loadable/component";
const Channel = loadable(() => import('@pages/SignUp'));
const DirectMessage = loadable(() => import('@layouts/Workspace'));
const Workspace: FunctionComponent = () => {
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, setNewWorkSpace] = useInput('');
const [newUrl, onChangeNewUrl, setNewUrl] = useInput('');
const { workspace } = useParams<{workspace: 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
);
const { data: memberData } = useSWR<IChannel[]>(
userData? `api/workspaces/${workspace}/channels` : null,
fetcher
);
const onLogout = useCallback(() => {
axios
.post('/api/users/logout', null, {
withCredentials: true,
})
.then(() => {
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(
'/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);
setShowInviteWorkspaceModal(false);
setShowInviteChannelModal(false);
}, []);
if(!userData) {
return <Redirect to="/login"/>
}
const toggleWorkspaceModal = useCallback(()=> {
setShowWorkspaceModal((prev) => !(prev));
},[]);
const onClickAddChannel = useCallback(() => {
setShowCreateChannelModal(true);
}, []);
const onClickInviteWorkspace = useCallback(() => {
setShowInviteWorkspaceModal(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}} 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>
<h2>Sleact</h2>
<button onClick={onClickInviteWorkspace}>워크스페이스에 사용자 초대</button>
<button onClick={onClickAddChannel}>채널 만들기</button>
<button onClick={onLogout}>로그아웃</button>
</WorkspaceModal>
</Menu>
<ChannelList/>
<DMList/>
{/* {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>
<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-url" 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
아래와 같이 작성해서 콘솔 찍어 보았는데, 'cleanup'은 찍히지 않고 똑같이 'DMList: workspace 바꼈다'만 무한히 찍힙니다.