해결된 질문
작성
·
6.7K
0
안녕하세요 제로초님, 현재 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;
답변 2
0
기존 코드에서 코드를 아래와 같이 바꿔서 해결했습니다!!
// App/index.tsx
<Route path="/workspace/:workspace/*" element={<Workspace />} />
⬆️ path에 /*
추가
// Workspace/index.tsx
<Workspaces>
{userData?.Workspaces?.map((ws) => {
return (
<Link key={ws.id} to={`/workspace/${ws.url}/channel/일반`}>
<WorkspaceButton>{ws.name.slice(0, 1).toUpperCase()}</WorkspaceButton>
</Link>
);
})}
<AddButton onClick={onClickCreateWorkspace}>+</AddButton>;
</Workspaces>
⬆️to 속성에 /workspace/
추가
모두 해피 코딩 하세요,,,^^!!
0
추가적으로 해본 방법은 Login과 Signup 컴포넌트에서 Navigate url을 수정해보았습니다.
if (data) {
return <Navigate to="/workspace/sleact/channel/일반" />;
}
if (data) {
return <Navigate to="/workspace/sleact" />;
}
// Workspace/index.tsx
const params = useParams<{ workspace?: string }>();
const { workspace } = params;
console.log(workspace) // sleact
위와 같은 결과가 나오게 되어 채널을 추가하거나 워크스페이스를 추가하는 등의 동작은 잘 됩니다.
하지만, 각 워크스페이스를 클릭했을 경우,
<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>
위의 코드에 의해서인지 http://localhost:3090/workspace/sleact/sleact/channel/일반 이렇게 잘못된 url로 이동하게 됩니다.. 결론적으로는 아직 해결하지 못했습니다😂