172만명의 커뮤니티!! 함께 토론해봐요.
해결됨
Slack 클론 코딩[실시간 채팅 with React]
안녕하세요 제로초님, 현재 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;
클론코딩 babel react socket.io Socket.io typescript 클론코딩 웹팩
mihyun Lee
2023-01-26T10:45:04.850Z
댓글 2
좋아요 0
조회수 7159
미해결
[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
안녕하세요, 제로초 스쿨 커뮤니티 에 들어갈 수 없는데..... 이제 슬랙을 운영하시지 않으시는건가요 ??
node.js express mysql mongodb typescript socket.io Socket.io nodejs jwt
스크루바
2023-01-26T02:33:10.097Z
댓글 2
좋아요 0
조회수 723
미해결
Slack 클론 코딩[실시간 채팅 with React]
백엔드에 배포하려고 파일 넘기기 전에,, 빌드가 잘 되었는지 테스트해보려고 (백엔드 먼저 켜두고) index.html을 그냥 브라우저로 열면 백엔드와 잘 연동될줄 알았는데, 첫 화면부터 보이지가 않아서요. 그 이유가 궁금합니다. 이유가 혹시,,, 웹펙에서 devServer: { historyApiFallback: true, // react router(원래 SPA에서는 3090/ 주소밖에 모른다. 뒤에 /login같은 경로는 우리가 가짜로 만들어낸건데 History api(History 기본함수)가 가짜주소를 만들어주는 거다.) port: 3090, devMiddleware: { publicPath: '/dist/' }, static: { directory: path.resolve(__dirname) }, proxy: { '/api/': { //프론트에서 /api/로 보내는 요청은 주소를 3095로 바꿔서 보내겠다 target: 'http://localhost:3095', changeOrigin: true, }, }, }, 위와 같이 dev모드일때는 3090포트 개발서버를 항상 켜두는데, 이처럼 뭔가 서버가 계속 켜있어야 프론트가 잘 작동 가능한건가요?? (그래서 배포할때 백엔드서버는 항상 켜있으니까 그냥 넘겨주면 된다는게 이해가 되는거 같아서요) 만약 그렇다면 프론트인데 빌드 결과물이 있는데 왜 계속 프론트만을 위한 서버가 켜있어야 하는지 궁금합니다,, 저는 배포파일이 실행파일마냥 그냥 index.html 실행하면 (백엔드 켜져있어서 통신할 수 있다는 가정하에) 알아서 잘 되는줄 알았거든요,,
웹팩 typescript socket.io babel babel react 클론코딩 Socket.io 클론코딩
초간단
2023-01-25T14:24:07.819Z
댓글 1
좋아요 0
조회수 593
미해결
Slack 클론 코딩[실시간 채팅 with React]
제로초님, 코드를 따라친 후에 로그아웃을 하고 다시 로그인 하면 이런 에러메세지가 뜹니다. 그런데 네트워크 탭을 보면 로그인이 정상적으로 된거 같아서 새로고침을 하면 에러 메세지가 사라지고 슬랙에서 로그인된 화면이 제대로 뜹니다. 근데 또 여기서 워크스페이스를 생성하려고 하면 콘솔에 axioserror메세지가 떠서 어떻게 해야될지 모르겠습니다.. Workspace/index.tsx import axios from "axios"; import React, { FC, useCallback, useState } from "react"; import useSWR from 'swr'; import fetcher from "@utils/fetcher"; import { Navigate, Routes, Route } from "react-router-dom"; import { AddButton, Channels, Chats, Header, LogOutButton, MenuScroll, ProfileImg, ProfileModal, RightMenu, WorkspaceButton, WorkspaceName, Workspaces, WorkspaceWrapper } from "@layouts/Workspace/style"; import gravatar from 'gravatar'; import loadable from '@loadable/component'; import Menu from "../../components/Menu"; import Modal from "../../components/Modal"; import { Link } from "react-router-dom"; import { IUser } from "@typings/db"; import { Button, Input, Label } from "@pages/SignUp/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: FC<React.PropsWithChildren<{}>> = ({children}) => { const [showUserMenu, setShowUserMenu] = useState(false); const [showCreateWorkspaceModal, setShowCreateWorkspaceModal] = useState(false); const [newWorkspace, onChangeNewWorkspace, setNewWorkspace] = useInput(''); const [newUrl, onChangeNewUrl, setNewUrl] = useInput(''); // revalidate = 서버로 요청 다시 보내서 데이터를 다시 가져옴 // mutate = 서버에 요청 안보내고 데이터를 수정 const {data: userData, error, mutate} = useSWR<IUser | false>('/api/users', fetcher, { dedupingInterval: 2000, }); const onLogout = useCallback(() => { axios.post('/api/users/logout', null , { withCredentials: true, }) .then(() => { mutate(false, false); }) }, []); const onClickUserProfile = useCallback((e: any) => { e.stopPropagation(); setShowUserMenu((prev) => !prev); }, []) const onClickCreateWorkspace = useCallback(() => { setShowCreateWorkspaceModal(true); }, []) const onCreateWorkspace = useCallback((e: any) => { 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); }, []) if(!userData) { return <Navigate to="/login" /> } if(!userData) return null; 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.nickname, { 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>Sleact</WorkspaceName> <MenuScroll>menu scroll</MenuScroll> </Channels> <Chats> <Routes> <Route path="/channel" element={<Channel />} /> <Route path="/dm" element={<DirectMessage />} /> </Routes> </Chats> {/* Input이 들어있으면 별도의 컴포넌트로 빼는 것을 추천(input에 글자를 입력할 때마다 여기 있는 함수들이 다 리렌더링 되기 때문에 비효율적) */} </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>워크스페이스 이름</span> <Input id="workspace" value={newUrl} onChange={onChangeNewUrl} /> </Label> <Button type="submit">생성하기</Button> </form> </Modal> </div> ) } export default Workspace; Modal/index.tsx import React, { useCallback, FC } from "react"; import { CloseModalButton, CreateModal } from "./style"; interface Props { show: boolean; onCloseModal: () => void; children: React.ReactNode; } const Modal: FC<Props> = ({show, children, onCloseModal}) => { const stopPropagation = useCallback((e: any) => { e.stopPropagation() }, []); if(!show){ return null; } return( <CreateModal onClick={onCloseModal}> <div onClick={stopPropagation}> <CloseModalButton onClick={onCloseModal}>×</CloseModalButton> {children} </div> </CreateModal> ); }; export default Modal; swr2.0 버전, react v18, typescript v18 swr을 최신버전 사용해서 revalidate대신 mutate를 사용했는데 제가 잘못 사용한건지 모르겠습니다.
클론코딩 babel typescript socket.io 웹팩 react 클론코딩 Socket.io
성창수
2023-01-25T06:57:14.751Z
댓글 2
좋아요 0
조회수 1887
미해결
Slack 클론 코딩[실시간 채팅 with React]
제로초님 영상을 보고 따라하는 도중 Menu의 index.tsx작성 후에 Workspace로 옮겼는데, Module을 찾을 수 없다고 뜨는데, 어디가 잘못 된건지 모르겠습니다... components/Menu/index.tsx import React, { FC } from 'react'; import { CreateMenu } from './style'; const Menu: FC<React.PropsWithChildren<{}>> = ({ children }) => { return ( <CreateMenu> <div>menu</div> {children} </CreateMenu> ); }; export default Menu; Workspace/index.tsx import React, { FC } from 'react'; import { CreateMenu } from './style'; const Menu: FC<React.PropsWithChildren<{}>> = ({ children }) => { return ( <CreateMenu> <div>menu</div> {children} </CreateMenu> ); }; export default Menu; 에러메세지입니다. 혹시 몰라서 터미널에 뜬 에러메세지도 첨부하겠습니다
클론코딩 Socket.io typescript socket.io 웹팩 react 클론코딩 babel
성창수
2023-01-24T10:16:05.408Z
댓글 1
좋아요 0
조회수 389
미해결
Slack 클론 코딩[실시간 채팅 with React]
제로초님, layouts폴더에 App.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 Channel = loadable(() => import('@pages/Channel')); const App = () => { return ( <Routes> <Route path="/" element={<Navigate replace to="/login" />} /> <Route path="/login" element={<LogIn />} /> <Route path="/signup" element={<SignUp />} /> <Route path="/workspace/channel" element={<Channel />} /> </Routes> ) } export default App; Route의 4번째줄 path에 /workspace로 하면 로그아웃 할 때 제대로 작동하는데 저렇게 workspace/channel로 코드를 작성하면 로그아웃 할 때, 아래처럼 뜹니다 /를 하나만 붙여야 되는건가요? 나머지 코드들은 변경하지 않았습니다. Login 폴더 index.tsx import useInput from "@hooks/useInput"; import axios from "axios"; import React, { useCallback, useState } from "react"; import { Form, Label, Input, LinkContainer, Button, Header, Error} from './styles'; import {Link, Navigate} from 'react-router-dom'; import useSWR from 'swr'; import fetcher from "@utils/fetcher"; const LogIn = () => { const {data, error, mutate} = useSWR('/api/users', fetcher); const [logInError, setLogInError] = useState(false); const [email, onChangeEmail] = useInput(''); const [password, onChangePassword] = useInput(''); const onSubmit = useCallback((e: any) => { e.preventDefault(); setLogInError(false); axios .post( '/api/users/login', {email, password}, {withCredentials: true}, ) .then((response) => { mutate(response.data, false); }) .catch((error) => { setLogInError(error.response?.data?.statusCode === 401); }) }, [email, password, mutate]); if(data === undefined) { return <div>로딩중...</div> } if(data) { return <Navigate to="/workspace/channel" /> } 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; workspace.tsx import axios from "axios"; import React, { FC, useCallback } from "react"; import useSWR from 'swr'; import fetcher from "@utils/fetcher"; import { Navigate } from "react-router-dom"; const Workspace: FC<React.PropsWithChildren<{}>> = ({children}) => { // revalidate = 서버로 요청 다시 보내서 데이터를 다시 가져옴 // mutate = 서버에 요청 안보내고 데이터를 수정 const {data, error, mutate} = useSWR('/api/users', fetcher); const onLogout = useCallback(() => { axios.post('api/users/logout', null , { withCredentials: true, }) .then(() => { mutate(false, false); }) }, []); if(data === false) { return <Navigate to="/login" /> } return( <div> <button onClick={onLogout}>로그아웃</button> {children} </div> ) } export default Workspace; swr은 2버전입니다.
Socket.io 웹팩 socket.io babel typescript react 클론코딩 클론코딩
성창수
2023-01-23T14:40:30.903Z
댓글 1
좋아요 0
조회수 6090
미해결
Slack 클론 코딩[실시간 채팅 with React]
제로초님, 전 강의 듣고 할 때만 해도 잘 됐는데 오늘 쉬고 다시 해보니까 이런 오류 메세지가 뜹니다. 해결해보려고 해도 잘 안되네요.. Cannot read properties of undefined (reading 'data') SignUp 폴더에서 index.tsx에 문제가 있다고 떠서 코드 올립니다 import useInput from "@hooks/useInput"; import React, { useCallback, useState } from "react"; import { Form, Label, Input, LinkContainer, Button, Header, Error, Success} from './styles' import axios from "axios"; import {Link} from 'react-router-dom'; // 타입스크립트는 간단하게 말하면 변수, 매개변수, 리턴 값에 타입을 붙여줌 // 변수에는 딱히 타입을 붙여주지 않아도 된다 타입스크립트가 알아서 추론하기 때문에(리턴값도 마찬가지) 그러나 매개변수에는 붙여주어야됨 const SignUp = () => { const [email, onChangeEmail] = useInput('');//useInput은 커스텀 훅 const [nickname, onChangeNickName] = useInput(''); const [password, setPassword] = useState(''); const [passwordCheck, setPasswordCheck] = useState(''); const [mismatchError, setMismathError] = useState(false); const [signUpError, setSignUpError] = useState(''); const [signUpSuccess, setSignUpSuccess] = useState(false); const onChangePassword = useCallback((e: any) => { setPassword(e.target.value); setMismathError(e.target.value !== passwordCheck); // 함수 기준으로 외부 변수만 deps에 적어줌 내부 변수는 x }, [passwordCheck]); const onChangePasswordCheck = useCallback((e: any) => { setPasswordCheck(e.target.value); setMismathError(e.target.value !== password) }, [password]); const onSubmit = useCallback( (e: any) => { e.preventDefault(); if (!mismatchError && nickname) { console.log('서버로 회원가입하기'); setSignUpError(''); setSignUpSuccess(false); axios .post('/api/users', { email, nickname, password, }) .then((response) => { console.log(response); setSignUpSuccess(true); }) .catch((error) => { console.log(error.response); //여기가 문제가 있다고 뜹니다. setSignUpError(error.response.data); }) .finally(() => {}); } }, [email, nickname, password, passwordCheck, mismatchError], ); 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="nickname-label"> <span>닉네임</span> <div> <Input type="text" id="nickname" name="nickname" value={nickname} onChange={onChangeNickName} /> </div> </Label> <Label id="password-label"> <span>비밀번호</span> <div> <Input type="password" id="password" name="password" value={password} onChange={onChangePassword} /> </div> </Label> <Label id="password-check-label"> <span>비밀번호 확인</span> <div> <Input type="password" id="password-check" name="password-check" value={passwordCheck} onChange={onChangePasswordCheck} /> </div> {mismatchError && <Error>비밀번호가 일치하지 않습니다.</Error>} {!nickname && <Error>닉네임을 입력해주세요.</Error>} {signUpError && <Error>{signUpError}</Error>} {signUpSuccess && <Success>회원가입되었습니다! 로그인해주세요.</Success>} </Label> <Button type="submit">회원가입</Button> </Form> <LinkContainer> 이미 회원이신가요? <Link to="/login">로그인 하러가기</Link> </LinkContainer> </div> ); }; export default SignUp;
웹팩 react babel socket.io typescript 클론코딩 Socket.io 클론코딩
성창수
2023-01-23T07:56:20.941Z
댓글 1
좋아요 0
조회수 2389
미해결
Slack 클론 코딩[실시간 채팅 with React]
https://github.com/ZeroCho/sleact에서 code로 가서 zip파일을 다운 받았습니다. 이후 git clone https://github.com/zerocho/sleact 명령어를 입력한 후, back 폴더에서 npm i까지 마쳤는데, 저는 강의 내용과 다르게 config 폴더가 비어 있습니다. 어떤 부분에서 잘못 따라 하였는이해를 못해서 깃 관련 구글링을 해보다가 결국 질문 드립니다.
react typescript 웹팩 socket.io Socket.io 클론코딩 babel 클론코딩
2023-01-22T19:46:21.614Z
댓글 1
좋아요 0
조회수 594
미해결
Slack 클론 코딩[실시간 채팅 with React]
tsconfig.json(ts -> js)로 바꿔주고, 그 후에 webpack.config.ts가 js,css,html들을 하나로 번들링해주는 순서가 맞나요?? $webpack 실행하면 웹팩 프로그램이 그냥 알아서 tsconfig.json이 있으면 먼저 적용한후 webpack.config.ts를 적용하는건가요?
클론코딩 Socket.io socket.io react babel 웹팩 클론코딩 typescript
초간단
2023-01-22T10:06:58.209Z
댓글 1
좋아요 0
조회수 499
미해결
Slack 클론 코딩[실시간 채팅 with React]
안녕하세요 강사님, 강의 중 채널 생성과정에서 오류가 있어 문의를 남깁니다. 먼저, 채널 데이터를 가져오지 못해 다른 문의를 찾아보니 seed 설정을 해주지 않은거 같아 npx sequelize db:create 진행하였고, 이어서 npx sequelize db:seed:all 과정을 진행했습니다 npx sequelize db:seed:all 을 진행하니 Validation error가 발생했습니다. 다른 문의를 보니 db에 sleact table이 생성되지 않아 발생하는 문의글이 많아서 db 사진 올려드릴게요 추가적으로, back 파일에서 npm run dev를 실행하면 db 연결까지 잘 됩니다. 도대체 어디가 잘 못 된 건지 감이 오지 않습니다.. 감사합니다.
react babel socket.io typescript 웹팩 클론코딩 Socket.io 클론코딩
안찬익
2023-01-21T07:18:15.709Z
댓글 1
좋아요 0
조회수 1121
미해결
Slack 클론 코딩[실시간 채팅 with React]
제로초님, 코드는 정확히 따라 한 거 같은데, 무엇이 문제인지 판단이 안되네요... 리액트 v18, 타입스크립트 v18, axios는 1.2.3 입니다. webpack import path from 'path'; import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import webpack, { Configuration as WebpackConfiguration } from "webpack"; import { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server"; interface Configuration extends WebpackConfiguration { devServer?: WebpackDevServerConfiguration; } import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; const isDevelopment = process.env.NODE_ENV !== 'production'; const config: Configuration = { name: 'sleact', mode: isDevelopment ? 'development' : 'production', devtool: !isDevelopment ? 'hidden-source-map' : 'eval', resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], alias: { '@hooks': path.resolve(__dirname, 'hooks'), '@components': path.resolve(__dirname, 'components'), '@layouts': path.resolve(__dirname, 'layouts'), '@pages': path.resolve(__dirname, 'pages'), '@utils': path.resolve(__dirname, 'utils'), '@typings': path.resolve(__dirname, 'typings'), }, }, entry: { app: './client', }, module: { rules: [ { test: /\.tsx?$/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { targets: { browsers: ['IE 10'] }, debug: isDevelopment, }, ], '@babel/preset-react', '@babel/preset-typescript', ], env: { development: { plugins: [['@emotion', { sourceMap: true }], require.resolve('react-refresh/babel')], }, production: { plugins: ['@emotion'] } }, }, exclude: path.join(__dirname, 'node_modules'), }, { test: /\.css?$/, use: ['style-loader', 'css-loader'], }, ], }, plugins: [ new ForkTsCheckerWebpackPlugin({ async: false, // eslint: { // files: "./src/**/*", // }, }), new webpack.EnvironmentPlugin({ NODE_ENV: isDevelopment ? 'development' : 'production' }), ], output: { path: path.join(__dirname, 'dist'), filename: '[name].js', publicPath: '/dist/', }, devServer: { historyApiFallback: true, // react router port: 3090, devMiddleware: { publicPath: '/dist/' }, static: { directory: path.resolve(__dirname) }, proxy: { '/api/': { target: 'http://localhost:3095', changeOrigin: true, }, }, }, }; if (isDevelopment && config.plugins) { config.plugins.push(new webpack.HotModuleReplacementPlugin()); config.plugins.push(new ReactRefreshWebpackPlugin()); } if (!isDevelopment && config.plugins) { } export default config; index.tsx import useInput from "@hooks/useInput"; import React, { useCallback, useState } from "react"; import { Form, Label, Input, LinkContainer, Button, Header, Error} from './styles' import axios from "axios"; const SignUp = () => { const [email, onChangeEmail] = useInput('');//useInput은 커스텀 훅 const [nickname, onChangeNickName] = useInput(''); const [password, setPassword] = useState(''); const [passwordCheck, setPasswordCheck] = useState(''); const [mismatchError, setMismathError] = useState(false); const onChangePassword = useCallback((e: any) => { setPassword(e.target.value); setMismathError(e.target.value !== passwordCheck); // 함수 기준으로 외부 변수만 deps에 적어줌 내부 변수는 x }, [passwordCheck]); const onChangePasswordCheck = useCallback((e: any) => { setPasswordCheck(e.target.value); setMismathError(e.target.value !== password) }, [password]); const onSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); if(!mismatchError && nickname){ console.log('서버로 회원가입하기'); axios.post('/api/users', { email, nickname, password, }) .then((response) => { console.log(response); })//요청이 성공하면 실행 .catch((error) => { console.log(error.response); })//요청이 실패하면 실행 .finally(() => {});//성공하든 실패하든 실행시키고 싶은 것 } console.log(email, nickname, password, passwordCheck) }, [email, nickname, password, passwordCheck, mismatchError]); 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="nickname-label"> <span>닉네임</span> <div> <Input type="text" id="nickname" name="nickname" value={nickname} onChange={onChangeNickName} /> </div> </Label> <Label id="password-label"> <span>비밀번호</span> <div> <Input type="password" id="password" name="password" value={password} onChange={onChangePassword} /> </div> </Label> <Label id="password-check-label"> <span>비밀번호 확인</span> <div> <Input type="password" id="password-check" name="password-check" value={passwordCheck} onChange={onChangePasswordCheck} /> </div> {mismatchError && <Error>비밀번호가 일치하지 않습니다.</Error>} {!nickname && <Error>닉네임을 입력해주세요.</Error>} {/* {signUpError && <Error>{signUpError}</Error>} */} {/* {signUpSuccess && <Success>회원가입되었습니다! 로그인해주세요.</Success>} */} </Label> <Button type="submit">회원가입</Button> </Form> <LinkContainer> 이미 회원이신가요? <a href="/login">로그인 하러가기</a> </LinkContainer> </div> ); }; export default SignUp; 이렇게 따라 한 후에 회원 가입 누르면 아래 같은 화면이 뜹니다.
웹팩 react socket.io Socket.io typescript babel 클론코딩 클론코딩
성창수
2023-01-20T09:23:14.775Z
댓글 2
좋아요 0
조회수 1127
미해결
파이썬 무료 강의 (활용편3) - 웹 스크래핑 (5시간)
안녕하세요 먼저 좋은 강의 해주시는 나도코딩님께 감사드립니다. 이전에 나름 정성들여 글을 작성했는데, 확인이 안돼서 다시 작성해봅니다. 제가 강의를 보고있는 2023년 1월 19일과 강의날인 2020년 8월과 네이버 항공권 인터페이스가 많이 상이하고, 셀레니움과 파이썬 버전이 달라 저의경우 아래와 같이 코드를 작성했습니다. 다른분들께 도움이 되었으면 좋겠습니다. 현재 저의 환경의 경우 python버전 3.10.7 버전이고 selenium또한 강의때 사용하신것 보다 더 높은 것으로 보입니다. 현재 제 경우와 강의내용과 상이한 부분은 아래와 같습니다. 네이버 항공권 진입시, "지금 바로 혜택 확인하기" 광고 팝업창 발생, 해당부분 get_attribute메소드를 사용해 title을 비교해, 팝업이 있다면 팝업을 삭제하는 XPATH를 확인해 해당 엘리먼트를 클릭하도록 구현 browser.find_element_by_class_name과 같이 find_element_by_~~ 메소드 사용불가 => 현재 제가 사용중인 셀레니움 버전이 상위 버전으로 보이는데, 이는 3 line과 같이 import By로 쉽게 수정 가능 find_elements_link_text 메소드 사용 불가 => 2020년대비 2023년 현재 네이버 항공권 웹페이지 인터페이스의 변화때문인지 "가는 날" 및 날짜 선택을 위해 해당 메소드 사용시 빈리스트만 반환 "가는 날"은 XPATH로 엘리먼트를 얻어와 클릭하도록 구현 원하는 날짜의 경우 class이름을 확인해 모든 날짜정보를 갖고와 원하는 날짜정보만 사용하도록 구현 (compute_date 함수 참고 부탁 드립니다.) 추가적으로, 이 경우에 1년치 달력정보를 모두 읽어와 속도가 매우 느린데 더 좋은 방법을 알고계신분은 공유 부탁드립니다. ( _ _ ) compute_date(dates, 30)[1].click() 코드를 이렇게 수정해 오는날을 2월 30일을 선택하려 했으나 3월 30일을 선택해, 코드 오류인줄 알고 디버깅해보니 2월달은 30일이 없었습니다...ㅎ 정상동작 중 입니다. 저와같이 초반에 고생하시는 분들께 조금이나마 도움이 되었으면 좋겠습니다. 감사합니다. import time from selenium import webdriver from selenium.webdriver.common.by import By URL = "https://flight.naver.com/" def compute_date(dates, target) -> list: print("compute entire date information from current month to limit") return [date for date in dates if date.text == str(target)] browser = webdriver.Chrome() # browser.maximize_window() browser.get(URL) time.sleep(1) # remove AD popup find = browser.find_elements(By.CLASS_NAME, "anchor") for f in find: if f.get_attribute("title") == "지금 바로 혜택 확인하기": browser.find_element(By.XPATH, '//*[@id="__next"]/div/div[1]/div[9]/div/div[2]/button[1]').click() print("remove pop up") break # push 가는 날 button also delay 1 sec is necessary, if don't use this, can not read date information. browser.find_element(By.XPATH, '//*[@id="__next"]/div/div[1]/div[4]/div/div/div[2]/div[2]/button[1]').click() time.sleep(1) # read date information and push 25 day dates = browser.find_elements(By.CLASS_NAME, "sc-evZas dDVwEk num".replace(" ", ".")) compute_date(dates, 25)[0].click() # read date information and push 30 day dates = browser.find_elements(By.CLASS_NAME, "sc-evZas dDVwEk num".replace(" ", ".")) compute_date(dates, 30)[0].click() while True: pass
selenium python 웹-크롤링 웹 스크래핑
박재현
2023-01-19T15:24:14.819Z
댓글 2
좋아요 7
조회수 2584
미해결
코딩테스트 [ ALL IN ONE ]
안녕하세요! Leetcode daily temperature 문제 해설을 보다 질문이 생겨서 남깁니다! while 문으로 stack을 돌면서 대소 비교를 하는데, 여기서의 시간복잡도가 O(n)이 될 걱정은 안해도 되나요? 어렴풋이 생각하기에, stack에 temperatures의 원소가 들어가기 때문에 n개의 원소가 들어갈 것이고, for문으로 길이가 n인 temperatures를 돌면 결과적으로 worst case로 O(n^2)이 될 수도 있지 않나 ... 라는 생각이 들었습니다. 시간복잡도 개념이 아직 부족해서 헷갈리는 것 같은데 제가 어떤 부분을 간과했을까요?
python algorithm 코테 준비 같이 해요!
강욱
2023-01-18T07:51:37.094Z
댓글 1
좋아요 5
조회수 533
미해결
프로그래밍 시작하기 : 파이썬 입문 (Inflearn Original)
안녕하세요 선생님 강의자료 부탁드립니다 b_9108@naver.com 입니다 감사합니다
2023-01-17T14:57:39.239Z
댓글 1
좋아요 0
조회수 287
미해결
[개정판] 파이썬 머신러닝 완벽 가이드
1명의 확실한 P 예측과 나머지를 모두 N으로 예측했을 때 정밀도가 100%가 된다고 말씀하셨습니다. 이 경우에 재현율도 1 / 1+0 해서 100%되지 않나요?
Munju Ham
2023-01-16T11:37:40.303Z
댓글 1
좋아요 0
조회수 299
미해결
장고 설계철학으로 시작하는 파이썬 장고 입문
안녕하세요 강사님. 어제 시작한 사람입니다! 다름이 아니라, 개발 환경 구축 중에 터미널에서 파이썬 버전 설치 단계가 계속 에러가 뜹니다. PS C:\Users\ped> pyenv install 3.11.1 :: [Info] :: Mirror: https://www.python.org/ftp/python :: [Installing] :: 3.11.1 ... :: [Error] :: error installing "core" component MSI. :: [Error] :: couldn't install 3.11.1 다른 버전도 모두 마찬가지입니다! 공장 초기화를 해도 이렇게 뜹니다.. ++ 그래서 파이썬 공식 홈페이지에 가서 파이썬을 수동 설치했습니다. 이후 다시 따라가려니(글로벌 버전 지정), 또 에러가 뜹니다. PS C:\Users\ped> pyenv global 3.11.1 pyenv specific python requisite didn't meet. Project is using different version of python. Install python '3.11.1' by typing: 'pyenv install 3.11.1' 어떻게 해야 할 까요.. 진도를 못 나가서 답답합니다 ㅠㅠ
kjkim
2023-01-14T15:06:22.682Z
댓글 2
좋아요 0
조회수 1673
미해결
파이썬 무료 강의 (기본편) - 6시간 뒤면 나도 개발자
print("Redd\bApple") << RedApple이 되는 것은 알겠는데요 시험삼아 print("Redd\b")를 출력했더니 Redd그대로 나오더라구요?? \b뒤에 어떤 문자열이 있어야 앞의 한 글자를 지울 수 있는 건가요??
Ambition
2023-01-12T15:19:41.220Z
댓글 1
좋아요 0
조회수 345
미해결
[개정판] 파이썬 머신러닝 완벽 가이드
안녕하세요 선생님! 지난번에 보내주신 쿠폰은 정말 감사했습니다. 덕분에 다음으로 공부할 것에 대해 계획할 수 있었습니다. 개인 공부중에 궁금한 것이 있어 이렇게 여쭤봅니다. 제가 사이버보안 분야를 머신러닝으로 향상시키는 것에 관심이 있는데요. 마침 앙상블 수업을 듣던 와중에 개인적으로 공부하고 있던 IDS(침입탐지) 데이터 셋 관련해서 어떤 아이디어가 생겨서요. 이 데이터셋은 다중 분류에 해당되는 것인데요. 공격 패턴이 15가지(라벨) 정도라고 가정하겠습니다.이 라벨들을 저만의 방법으로 새로 분류하여 새로운 라벨들을 추가하여 만들어서 각각 학습하고 테스트를 진행해봤습니다. 첫번째로, 이진 분류로 라벨을 0, 1로 다시 만들어서 모든 공격들은 1로 분류하고, 일반적인 것(양성,Benign)들은 0으로 분류하는 것이죠. 즉, 공격이긴 공격인데 어떤 공격인지는 모르는 정도로만 시스템이 탐지하는 것입니다. 이럴 경우, 정확도, 정밀도, 재현율이 모두 99.97%로 아주 높은 확률로 탐지를 해냅니다. 두번째 분류는 비슷한 공격 패턴끼리 묶어서 그룹화를 한 뒤(즉, 라벨의 수를 줄이는 것입니다.) 학습 및 테스트를 하는 것입니다. 이럴 경우에도 아주 높은 점수를 보였습니다. 마지막 분류는 원래 라벨들입니다(전처리 작업 후). 역시 높은 점수를 보이긴 하나, 약간 낮은 점수를 보이는 일부 라벨들이 있습니다.그래서 말인데요. 혹시 이 세가지 모델을 연결해서 결과를 추출하는 방법이 있을 까요? 예를 들어, 먼저 공격인지 아닌지를 보고(첫번째 분류), 공격이라면 어떤 그룹에 속하는 지 보고(두번 째 분류), A라는 그룹이라면, 마지막 단계의 분류에서 세부적으로 어떤 라벨인지(세번째 분류)를 예측해내는 것이죠. 이렇게 연계하여 결과를 예측할 수 있는 모델을 만든다면, 점수가 약간 낮은 라벨들도 일부 보완할 수 있지 않을 까 생각되어서요. 아이디어는 떠올랐는데, 구글에 검색해보니 앙상블이나, 파이프라인 등의 관련된 내용만 검색이 되어서요. 혹시 제가 모르는 어떤 기술이 있을까요? 선생님의 조언을 구합니다.
Samuel Jeon
2023-01-11T14:41:49.549Z
댓글 1
좋아요 0
조회수 407
미해결
웹 자동화 프로그램 만들기(파이썬 + 셀레니움)
from selenium import webdriver driver = webdriver.Chrome("./chromedriver") 이렇게 치면 화면이 잠깐 떳다가 바로 중단되서 사라지는데 실행 중단을 막을수 없을까요?
안현정
2023-01-10T01:17:47.252Z
댓글 2
좋아요 1
조회수 2217
해결됨
프로그래밍 시작하기 : 파이썬 입문 (Inflearn Original)
append 함수를 사용하면 (10)을 사용하면 마지막에 10을 넣고 ('10')을 사용하면 '10'을 넣었습니다 (asdf)를 사용하면 오류가 나던데 인트형, 실수형이나 ''가 붙어야 사용 가능한가요? 또한 extend 함수에서 a.extend(b) print('a.extend(a)=',a) 라고 적으면 오류가 나지 않았지만 print('a.extend(a)=',a.extend(b)) 는 none라는 문장만 나왔습니다. extend는 따로 실행을 시켜 줘야 되는 함수 인가요? 마지막으로 처음에 b=a를 해놓고 a만 append, sort, reverse 등 함수를 적용시켰으나 마지막에 보니 b도 같이 변환되어 있었습니다. 여태 것 다른 함수들은 b=a를 한 후 a값을 변동시켜도 b의 값은 초기 a값 그대로였는데 b도 같이 변화시키는 함수들이 따로 있는건가요??
정호윤
2023-01-09T08:46:26.304Z
댓글 1
좋아요 0
조회수 489