• 카테고리

    질문 & 답변
  • 세부 분야

    프론트엔드

  • 해결 여부

    미해결

Cannot read properties of undefined (reading 'map')

23.01.25 15:57 작성 조회수 1.33k

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

성창수님의 프로필

성창수

질문자

2023.01.26

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 만 해도 됩니다

성창수님의 프로필

성창수

질문자

2023.01.27

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

0

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

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

성창수님의 프로필

성창수

질문자

2023.01.25

넵!

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

image

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

image

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

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

성창수님의 프로필

성창수

질문자

2023.01.26

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

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

workspace 생성 성공했을 때

workspace 실패했을 때

네트워크 탭입니다

콘솔 에러메세지입니다

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

성창수님의 프로필

성창수

질문자

2023.01.26

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

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

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

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