인프런 커뮤니티 질문&답변

성창수님의 프로필 이미지
성창수

작성한 질문수

Slack 클론 코딩[실시간 채팅 with React]

모달 만들기

Cannot read properties of undefined (reading 'map')

작성

·

1.7K

0

제로초님, 코드를 따라친 후에 로그아웃을 하고 다시 로그인 하면 이런 에러메세지가 뜹니다.

그런데 네트워크 탭을 보면 로그인이 정상적으로 된거 같아서 새로고침을 하면 에러 메세지가 사라지고 슬랙에서 로그인된 화면이 제대로 뜹니다.

근데 또 여기서 워크스페이스를 생성하려고 하면 콘솔에 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}>&times;</CloseModalButton>
        {children}
      </div>
    </CreateModal>
  );
};

export default Modal;

swr2.0 버전, react v18, typescript v18

swr을 최신버전 사용해서 revalidate대신 mutate를 사용했는데 제가 잘못 사용한건지 모르겠습니다.

답변 2

0

성창수님의 프로필 이미지
성창수
질문자

userData.Workspaces가 undefined인데 map이 돼서 오류가 난거 같아서 코드를

 

        <Workspaces>
          {userData.Workspaces !== undefined
          ?userData?.Workspaces.map((ws) => {
            return (
              <Link key={ws.id} to={`/workspace/${123}/channel`}>
                <WorkspaceButton>{ws.name.slice(0, 1).toUpperCase()}</WorkspaceButton>
              </Link>
            )
          })
          : null
        }
          <AddButton onClick={onClickCreateWorkspace}>+</AddButton>
        </Workspaces>

이렇게 변경하니까 에러가 해결되네요

조언해주셔서 감사합니다!

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

userData?.Workspaces?.map 만 해도 됩니다

성창수님의 프로필 이미지
성창수
질문자

오 더 쉬운 방법이 있었네요 감사합니다!

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

네트워크탭에서 빨간색이 있으면 그걸 누르고 헤더를 먼저 보여주세요. 그게 에러 해결의 실마리입니다.

그리고 network error로 되어있으므로 서버 에러 메시지도 보여주세요.

성창수님의 프로필 이미지
성창수
질문자

넵!

이게 워크스페이스 생성될 때 오류 헤더이고,

image

서버에서는 이렇게 뜹니다.

image

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

헤더탭 왼쪽에 있는 x를 눌러서 네트워크탭을 보여주세요. 지금 에러메시지가 뭔가 가려져있어서 문제 파악이 안 됩니다. 콘솔에 빨간에러 6개와 추가로 1개가 더 있는데 전부 알아야 합니다.

그리고 api 요청시 3095로 하시는 이유가 있나요? proxy 서버를 사용하세요.

성창수님의 프로필 이미지
성창수
질문자

그 마지막 영상 부분에 api 요청을 3095로 바꿔서 그대로 똑같이 바꿨습니다!

말씀해주신대로 proxy서버로 바꿔서 사용했는데 워크스페이스를 생성하면 처음에는 성공하는데 그 후부터는 성공하지 않습니다.

workspace 생성 성공했을 때

workspace 실패했을 때

네트워크 탭입니다

콘솔 에러메세지입니다

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

같은 이름으로 생성하니 안 되는 것 아닌가요?

성창수님의 프로필 이미지
성창수
질문자

오 일단 워크스페이스까지는 다 됐습니다 감사합니다!!

근데 로그인 할 때 에러메세지는 왜 뜨는 건가요?

image저 상태에서 새로고침하면 로그인된 화면으로 전환되는데 오류가 아닌건가요?

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

저기 에러 위치가 나와있습니다. 그 부분이 undefined인 겁니다. 새로고침 부분은 지금은 문제인지 알 수 없습니다. 에러부터 해결하고 봐야 합니다.

성창수님의 프로필 이미지
성창수

작성한 질문수

질문하기