미해결
Slack 클론 코딩[실시간 채팅 with React]
채널 생성시 404오류
안녕하세요!!! 강좌 열심히 따라하면서 배우는 중, 해결점을 찾을수 없는 부분이 있어서 질문합니다.
*우선 리액트 v6를 사용하여 route가 아래와 같이 되었다는 것을 참고해주세요.
**SWR역시 revalidate가 작동이 안되어, mutate사용하였습니다.
[이슈사항]
채널 리스트는 잘 받아오는 것 같습니다.
하지만, 채널생성시 404에러가 발생합니다.
서버는 "존재하지 않는 워크스페이스입니다."라는 응답을 줍니다.
의심되는 것은 버전6 라우팅설정에 문제가 있지 않을까 생각되는데... 검색해봐도 딱히 잘못된것을 못 발견해서 이렇게 질문남깁니다.
//CreateChannelModal 코드
import Modal from '@components/Modal';import React, { CSSProperties, useCallback, VFC } from 'react';import { Button, Input, Label } from '@pages/SignUp/styles';import useInput from '@hooks/useinput';import axios from 'axios';import { useParams } from 'react-router';import { toast } from 'react-toastify';import useSWR from 'swr';import { IChannel, IUser } from '@typings/db';import fetcher from '@utils/fetcher';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, error, mutate } = useSWR<IUser | false>('http://localhost:3095/api/users', fetcher); const { data: channelData, mutate: mutateChannel } = useSWR<IChannel[]>( // 로그인하지 않은상태이면 호출안하고, null로 userData ? `http://localhost:3095/api/workspaces/${workspace}/channels` : null, fetcher, ); const onCreateChannel = useCallback( (e) => { 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], ); 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;
//Workspace 코드import React, { VFC, useCallback, useState } from 'react';import useSWR, { mutate } from 'swr';import fetcher from '@utils/fetcher';import axios from 'axios';import { Routes, Route, Navigate, Link } from 'react-router-dom';import { Header, LogOutButton, ProfileImg, ProfileModal, RightMenu, WorkspaceWrapper, Workspaces, Channels, Chats, WorkspaceName, MenuScroll, WorkspaceButton, AddButton, WorkspaceModal,} from '@layouts/Workspace/styles';import gravatar from 'gravatar';import loadable from '@loadable/component';import Menu from '@components/Menu';import { IChannel, IUser } from '@typings/db';import { Button, Input, Label } from '@pages/SignUp/styles';import useInput from '@hooks/useinput';import Modal from '@components/Modal';import CreateChannelModal from '@components/CreateChannelModal';import { toast } from 'react-toastify';import { useParams } from 'react-router';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 [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>('http://localhost:3095/api/users', fetcher); const { data: channelData } = useSWR<IChannel[]>( // 로그인하지 않은상태이면 호출안하고, null로 userData ? `http://localhost:3095/api/workspaces/${workspace}/channels` : null, fetcher, ); const onLogout = useCallback((e) => { e.preventDefault(); axios .post('http://localhost:3095/api/users/logout', null, { withCredentials: true, }) .then((response) => { 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((response) => { mutate(response.data); 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 <Navigate replace to="/login" />; } return ( <div> <Header> <RightMenu> <span onClick={onClickUserProfile}> <ProfileImg src={gravatar.url(userData.email, { s: '28px', d: 'retro' })} alt={userData.email} /> {showUserMenu && ( <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.email}</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> {/*<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> {channelData?.map((v) => ( <div>{v.name}</div> ))} </MenuScroll> </Channels> <Chats> chats <Routes> <Route path="/channel/:channel" element={<Channel />} /> <Route path="/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} /> </div> );};export default Workspace;
//App코드입니다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={<LogIn />} /> <Route path="/login" element={<LogIn />} /> <Route path="/signup" element={<SignUp />} /> <Route path="/workspace/:workspace/*" element={<Workspace />} /> </Routes> );};export default App;