인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

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

fefefefe님의 프로필 이미지
fefefefe

작성한 질문수

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

Cannot read properties of undefined (reading 'map') 관련 질문드립니다.

작성

·

302

0

안녕하세요.

Cannot read properties of undefined (reading 'map') 하여 질문을 드리고자합니다.

문제는 해결 했으나 타입스크립트가 미숙하여 이런 현상이 발생한거 같은데 왜 이런 문제가 발생했는지 잘 몰라서 질문 드립니다.

 

오류가 발생 된 flow는

 

login 시 /workspace/sleact/channel/일반 으로 접을 했을 경우 

 

그림과 같은 에러가 발생하였습니다.

map 관련해서 초기 값이 안들어오는 부분의 에러인것을 확인하고 userData 옵셔널 체이닝을 에 해당 하는 값에 앞부분만 주었는데 에러가 발생한거 같습니다.

 

자세한 코드는 이렇습니다.

 

<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...

제로초님의 코딩을 전부 클론하여 작성하였는데 왜 이런 문제가 발생했는지 이해를 못하고 있습니다.

혹여 typescript에서는 전부 옵셔널 체이닝을 적용해줘야하나요 ... ??

 

세부적인 코드는 아래 첨부드립니다.

아 그리고 userData는 정상적으로 다 받아옵니다.

 

import fetcher from '@utils/fetcher';
import axios from 'axios';
import { type } from 'os';
import React, { useCallback, ReactNode, useState } from 'react';
import { Redirect, Route, Switch, useParams } from 'react-router';
import useSWR from 'swr';
import {
  Header,
  RightMenu,
  ProfileImg,
  WorkspaceWrapper,
  Workspaces,
  Channels,
  Chats,
  WorkspaceName,
  MenuScroll,
  ProfileModal,
  LogOutButton,
  WorkspaceButton,
  AddButton,
  WorkspaceModal,
} from '@layouts/Workspace/styles';
import gravatar from 'gravatar';
import Channel from '@pages/Channel';
import DirectMessage from '@pages/DirectMessage';
import Menu from '@components/Menu';
import { Link } from 'react-router-dom';
import { IChannel, IUser, IWorkspace } from '@typings/db';
import { Button, Input, Label } from '@pages/SignUp/styles';
import useInput from '@hooks/useInput';
import Modal from '@components/Modal';
import { toast } from 'react-toastify';
import CreateChannelModal from '@components/CreateChannelModal';

type Props = {
  children?: ReactNode;
};

function Workspace({ children }: Props) {
  const [showUserMenu, setShowUserMenu] = useState(false);
  const [showCreateWorkspaceModal, setShowCreateWorkspaceModal] = useState(false);

  const [showWorkSpaceModal, setShowWorkSpaceModal] = useState(false);
  const [showCreateChannelModal, setShowCreateChannelModal] = useState(false);

  const [newWorkSpace, onChangeNewSpace, setNewWorkSpace] = useInput('');
  const [newUrl, onChangeNewUrl, setNewUrl] = useInput('');

  const { workspace } = useParams<{ workspace: string }>();

  const {
    data: userData,
    error,
    revalidate,
    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(() => {
        // revalidate();
        mutate(false, false);
      });
  }, []);

  const onClickUserProfile = useCallback((e) => {
    e.stopPropagation();
    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(() => {
          revalidate();
          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 <Redirect to="/login" />;
  }

  return (
    <div>
      <Header>
        <RightMenu>
          {/* 우측 상단 프로필 active */}
          <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.email, { 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 onClick={toggleWorkSpaceModal}>Sleact</WorkspaceName>
          <MenuScroll>
            <Menu show={showWorkSpaceModal} onCloseModal={toggleWorkSpaceModal} style={{ top: 95, left: 80 }}>
              <WorkspaceModal>
                <button onClick={onClickAddChannel}>채널만들기</button>
                <button onClick={onLogout}>로그아웃</button>
              </WorkspaceModal>
            </Menu>
            {channelData?.map((v) => (
              <div>{v.name}</div>
            ))}
          </MenuScroll>
        </Channels>
        <Chats>
          <Switch>
            <Route path="/workspace/:workspace/channel/:channel" component={Channel} />
            <Route path="/workspace/:workspace/dm/:id" component={DirectMessage} />
          </Switch>
        </Chats>
      </WorkspaceWrapper>
      {/* 워크 스페이스 생성 모달 onClickCreateWorkspace */}
      <Modal show={showCreateWorkspaceModal} onCloseModal={onCloseModal}>
        <form onSubmit={onCreateWorkspace}>
          <Label id="workspace-label">
            <span>워크스페이스 이름</span>
            <Input id="workspace" value={newWorkSpace} onChange={onChangeNewSpace}></Input>
          </Label>
          <Label id="workspace-label">
            <span>워크스페이스 url</span>
            <Input id="workspace" value={newUrl} onChange={onChangeNewUrl}></Input>
          </Label>
          <Button type="submit">생성하기</Button>
        </form>
      </Modal>

      {/* 채널 만들기 모달 onClickAddChannel*/}
      <CreateChannelModal
        show={showCreateChannelModal}
        onCloseModal={onCloseModal}
        setShowCreateChannelModal={setShowCreateChannelModal}
      />
    </div>
  );
}

export default Workspace;

답변 1

0

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

userData가 있는게 확실하다면 userData.Workspaces가 없었던 것 같습니다.

근데 userData?.Workspaces?.map을 하셔서 최종적으로 문제가 해결된건가요?

워크스페이스 목록들이 전부 뜨나요? 그럼 크게 문제는 없습니다.

fefefefe님의 프로필 이미지
fefefefe
질문자

네네
userData?.Workspaces?.map 해서 해결 했고요.

Workspaces 목록은 array로 담겨 있습니다.

근데 이게 정확히 왜 그런지 모르겠네요.. 값이 있음에 불구하고 

fefefefe님의 프로필 이미지
fefefefe

작성한 질문수

질문하기