[에러] Cannot read properties of undefined (reading '데이터')

Joah·2022년 6월 24일
0

Error해결

목록 보기
1/1

에러 원인

작성하던 코드

  • 부모인 Main.js에서 Mockdata에서 받아온 데이터를 자식인 MainBox.js에 넘겨준다.
//Main.js

import React, { useEffect, useState } from "react";
import MainBox from "./components/MainBox/MainBox";
import "./Main.scss";

const Main = () => {
  const [movies, setMovies] = useState([]);
  useEffect(() => {
    fetch("/data/mainMock.json")
      .then(res => res.json())
      .then(data => {
        setMovies(data);
      });
  }, []);
    //useState를 사용하여 받아온 데이터를 movies에 담아주며 movies의 초깃값은 빈 배열이다.

  return (
    <>
      <div>
        <h1 className="fakeNav">안녕하세요</h1>
      </div>
      <div className="mainWrapper">
        <MainBox movies={movies} />
      </div>
    </>
   //Mockdata에서 받아온 데이터를 movies라는 이름으로 자식인 MainBox.js에 넘겨준다.
  );
};

export default Main;
  • 자식인 MainBox.js에서 movies를 props로 넘겨받는다.
  • 받아온 movies 데이터를 map함수를 적용하여 <Film> 컴포넌트에 각각의 속성을 넘겨준다.
import React, { useState, useEffect } from "react";
import Film from "./../Film/Film";
import "./MainBox.scss";

const MainBox = ({ movies }) => {
  return (
    <div className="mainBox">
    //미국고전영화를 Mockdata에서의 데이터를 삽입하려고 한다. movies의 key인 theme에 값이 저장되어있다.
      <p className="filmTheme">미국고전영화</p>
      <ul className="filmList">
        {movies.map(movie => (
          <Film slide={slidePx} key={movie.id} movie={movie} />
        ))}
      </ul>
      <div className="prevBtn" onClick={toPrev}>
        <i className="fa-solid fa-chevron-left" />
      </div>
      <div className="nextBtn" onClick={toNext}>
        <i className="fa-solid fa-chevron-right" />
      </div>
    </div>
  );
};

export default MainBox;

에러를 겪은 시점

<ul className="filmList">
  {movies.map(movie => (
   <Film slide={slidePx} key={movie.id} movie={movie} />
  ))}
</ul>

해당 map함수는 에러를 반환하지 않고 UI도 정상적으로 출력되었다.
그것을 확인하려면 아래의 이미지에서 영화제목, 년도, 국가, 평점, 이미지가 모두 잘 출력된 것!
(이미지는 사실 귀찮아서 하나의 url로 통일해서 모두 같은 이미지다.)

따라서 데이터가 movies에 잘 담겼고, moive 안에 있는 id, name, url 등 모두 출력할 수 있구나~~ 에러 없이 잘 흘러가네!! 라고 💥잘.못.생.각.했.다.


그래서 에러가 뭐여?

내가 하고 싶었던 것은 <p>태그 안의 미국고전영화을 Mockdata로 관리하여 각각의 컴포넌트에 다른 이름으로 출력하고 싶었다.

위의 이미지에서는 미국고전영화를 하드코딩으로 넣었다.
하지만 Mockdata에서 얻어온 데이터에서 뽑아 출력하고 싶다.
따라서 첫 번째 제목은 미국고전영화, 두 번째 목록에는 한국고전영화라는 제목으로 넣고 싶었다.

그렇다면, 넘겨받은 props인 movies.theme으로 불러오면 되지!

맞아! 그냥 간단하잖아!

<p className="filmTheme">{movies[0].theme}</p>

일단 첫 번째 리스트에는 movies의 0번째 인덱스 즉, 첫 번째 데이터의 theme의 value 값을 가져오는 것이다.
우리의 mockdata는 이렇게 생겼다.

[
  {
    "id": 1,
    "name": "개개",
    "release_date": 1920,
    "image_url": "/images/poster/TheGreatGatsby.jpg",
    "theme": "미국고전영화"
  },
  {
    "id": 2,
    "name": "츠츠",
    "release_date": 1920,
    "image_url": "/images/poster/TheGreatGatsby.jpg",
    "theme": "1970's films"
  }
 ]
//원래는 데이터가 10개 정도이지만 간략하게 복사했다.

끝! 새로고침 해볼까?


응? 왜?

아니 그대로 movies 넘겨주고 map함수 돌렸을 때는 잘만 출력했는데 이 간단한 코드 하나에 이렇게 피가 철철 흐른다고?

다시 생각 해보자 어디가 잘못되었나?

  • 분명 부모에서 MainBox.js로 props를 통해 넘겨주었다.
const MainBox = ({ movies }) => {}
  • 넘겨온 movies를 아래 map함수로 돌렸더니 에러 없이 잘 출력되었다. 그리고 자식인 Film.js에 잘 넘겨주었다.
<ul className="filmList">
  {movies.map(movie => (
   <Film slide={slidePx} key={movie.id} movie={movie} />
  ))}
</ul>

💥근데 왜 바로 위에 {movies[0].theme}이 출력이 안돼?

💥에러 메세지 Cannot read properties undefined는 뭐야?

💥왜 props가 undefined인거지? 분명 movies안에는 데이터가 있잖아??!!

에러를 만나고 왜? 라는 생각이 든다면 console.log( )를 하자

import React, { useState, useEffect } from "react";
import Film from "./../Film/Film";
import "./MainBox.scss";

const MainBox = ({ movies }) => {
  console.log(movies)
  return (
    <div className="mainBox">
      <p className="filmTheme">미국고전영화</p>
      <ul className="filmList">
        {movies.map(movie => (
          <Film slide={slidePx} key={movie.id} movie={movie} />
        ))}
      </ul>
      <div className="prevBtn" onClick={toPrev}>
        <i className="fa-solid fa-chevron-left" />
      </div>
      <div className="nextBtn" onClick={toNext}>
        <i className="fa-solid fa-chevron-right" />
      </div>
    </div>
  );
};

export default MainBox;
  • 분명 moviesundefined라고 했지? 그래 movies를 콘솔에 찍어보자

    뭐야... 왜 빈 배열이 출력되지...?

아하 모먼트

오케이.. 일단 "Cannot read properties of undefined (reading 'theme')" 에러가 뜨는 이유는
배열 안에 아무런 데이터가 없는데 그 빈 배열의 0번째 인덱스에 theme이라는 key를 찾으라고 그러니 컴터가 나 못 찾아!! 빈 배열인데 데이터가 없잖아!!

아니 근데 왜 movies가 빈 배열인거야?

Mockdata를 가져오는 fetch함수가 적힌 코드를 보러가자 Main.js

const Main = () => {
  const [movies, setMovies] = useState([]);
  useEffect(() => {
    fetch("/data/mainMock.json")
      .then(res => res.json())
      .then(data => {
        setMovies(data);
      });
  }, []);

아 맞다.... useStatemovies의 초깃값은 빈 배열이구나.... 헐 오키 여기까지 오키
그래서 처음 콘솔에 빈 배열이 출력되었구나.


어? 왜?

  1. 처음 렌더링 할 때, movies는 빈 배열이다.
  2. useEffectfetch함수를 실행하고 렌더링 되었을 때 그때 movies에 데이터가 담긴다.

해결방법

생각 생각....(여러 해결 방법이 있다.)
그럼 일단 처음에 렌더링 될때 빈 배열이 movies안에 담기고 그 다음 렌더링 할 때는 데이터가 movies에 담기니깐 movies가 빈 배열이 아닐 때 데이터를 출력하라고 작성하자

<p className="filmTheme">{movies.length && movies[1].theme}</p>

코드를 해석하자면 조건부 렌더링을 사용한다.
빈 배열일 때 movies.length는 0이기 때문에 alsy 값이다.
만약 false && 연산자 앞에 있다면 뒤의 명령은 실행하지 않는다.

따라서 빈 배열일때는 false를 반환하고 끝

만약 movies에 데이터가 담겨 빈 배열이 아니라면 movies.length의 값은 truthy가 되기 때문에 && 다음의 명령이 실행된다.


첫 번째는 movies가 초깃값인 빈 배열을 가지고 있을 때
두 번째는 movies에 데이터가 담겼을 때
총 두 번 렌더링이 된다.


그럼 map함수는 왜 되는 거...? 아무런 에러 없이

map함수는 빈 배열에서도 돌아간다. 따라서 지금 위에 작성된 map함수는 빈 배열일 때 한번 돌아가고 movies에 데이터가 담겼을 때 한 번 총 2번 돌아가고 있다.
빈 배열도 돌기 때문에 에러를 반환하지는 않지만 비효율적으로 돌아가게 된다.
지금은 map함수가 1개이고 거기에 연결된 자식 컴포넌트가 1개 이지만 depth가 깊어지고 복잡한 로직이면 시간이 오래 걸리고 메모리도 차지하게 된다.
따라서 map함수 안에도 해당 조건을 작성하는게 최적이다.

profile
Front-end Developer

0개의 댓글