• 카테고리

    질문 & 답변
  • 세부 분야

    프론트엔드

  • 해결 여부

    해결됨

비동기 통신에 있어서 질문남깁니다.(코드첨부)

22.08.08 11:12 작성 조회수 251

0

서버에서 데이터를 가져와 렌더링 해주고자 아래처럼코드를 짜봤는데, 

Uncaught TypeError: Cannot read properties of undefined (reading 'map')

위와 같이 lists를 못불러오는 것 같은 에러를 냅니다.

import React, {useState, useEffect, Fragment} from "react";

const token = localStorage.getItem('token');

function Main() {
const [lists, setLists] = useState([]);

useEffect(() => {
const getData = async () => {
try {
const res = await fetch('http://localhost:8080/todos', {
method: 'GET',
headers: {
'Authorization': token
},
})
const result = await res.json()
setLists(result)
} catch (error) {
console.log(error);
}
}
getData()
}, [])

return (
<div className="Main">
<h1>Main</h1>
<ul>
{lists.data.map(list =>
<li key={list.id}>
<h2>{list.title}</h2>
<h3>{list.content}</h3>
</li>)}
</ul>
</div>
);
}

export default Main;

이상한점은, 강사님의 코드로 몇줄 추가하면 잘 렌더링이 되는게 이해가 되지 않습니다ㅜㅜ

아래는 잘 렌더링이 되는 코드입니다.

import React, {useState, useEffect, Fragment} from "react";

const token = localStorage.getItem('token');

function Main() {

const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [lists, setLists] = useState([]);

useEffect(() => {
const getData = async () => {
try {
const res = await fetch('http://localhost:8080/todos', {
method: 'GET',
headers: {
'Authorization': token
},
})
const result = await res.json()
setLists(result)
setIsLoaded(true)
} catch (error) {
setIsLoaded(true)
setError(error)
}
}
getData()
}, [])

if (error) {
return <div>애러입니다! {error.message}</div>
} else if (!isLoaded) {
return <div>로딩중!</div>
} else {
return (
<div className="Main">
<h1>Main</h1>
<ul>
{lists.data.map(list =>
<li key={list.id}>
<h2>{list.title}</h2>
<h3>{list.content}</h3>
</li>)}
</ul>
</div>
);
}
}

export default Main;

위의 코드에서 lists가 undefined인 이유가 무엇일까요..?

답변 2

·

답변을 작성해보세요.

1

1. 초기 lists는 빈 배열로 할당이 됩니다.

(const [lists, setLists] = useState([]);로 인해서)

이 부분의 접근은 아주 좋습니다!

 

처음 답변에서 제대로 확인하지 못한 부분, 추가설명 드리겠습니다.

 

먼저, 접근할 때

1. 상태 초기화

2. 초기화된 상태로 렌더링

3. Effect 발생

4. Effect 또는 Event로 인한 setState

5. setState로 인한 업데이트 발생

6. 업데이트된 상태로 렌더링

이런 흐름을 인지하시는 것이 중요합니다. 이에 맞춰서 다음을 살펴보겠습니다.

 

최초 상태가 []일 때는 data라는 프로퍼티가 없는 상태입니다.

const lists = []

console.log(lists.data)

를 해보시면 undefined를 출력하는 것을 보실 수 있습니다.

즉 lists.data는 최초 빈 배열일 때 undefined인 상태입니다.

그렇다면 lists.data.map은 undefined.map을 한 것이나 마찬가지가 됩니다!

 

이 부분을 예외 케이스로 사용하기 위해 분기 처리를 잘 해주셨습니다! 

다만 왜 저렇게 되는지는 위에 언급한 접근 순서 부분을 이해하시면 더 좋을 것 같아요!

 

그리고 아마 [{},…,{}]가 바로 들어오는 것이 아닌 {data:[{},…,{}]} 형식으로 받아오게 될 것입니다. 이 부분이 lists에 넣어지게 되는 것이고요!

이때 setLists(result)를 실행하면 이때 다시 한번 렌더링이 됩니다.

이제야 원하는 형태인 lists.data.map을 실행할 수 있게 되는 것이죠!

 

흐름은 이렇습니다.

  1. 최초 데이터 초기화(lists는 [])

  2. 초기화된 데이터를 통해 렌더링(lists가 []일 때)

  3. 렌더링이 되고 useEffect 실행

  4. setLists로 인해 lists가 {data:[{},…,{}]} 형식으로 바뀜

  5. 바뀐 상태(lists.data.map)를 통해 렌더링 진행

핵심은 "state가 어떤 값을 가졌는지 파악하지 못한 상태로 사용하고 있었다" 입니다!

 

 

- 초기에 데이터를 다 불러오지 못했을 때

- 에러가 났을 때

- 잘 불러와졌을 때

 

해당 부분 3가지를 언급해주셨는데 좋은 접근입니다. 강의에서 안내된 

 const [error, setError] = useState(null);

 const [isLoaded, setIsLoaded] = useState(false);

 const [lists, setLists] = useState([]);

 

코드가 언급해주신 부분을 state로 나타내고 있는 것이지요.

 

리액트에서는 이런 부분을 상태를 가지고 핸들링합니다!

조금 더 넓은 범위에서 리액트 또는 JS 애플리케이션 관점에서 들여다보면, 

"상태가 어떻게 초기화될 것인지, 상태가 어떻게 바뀔 것인지" 고려하면서 진행하면 좋을 것 같습니다. 

감사합니다.

 

jj4783님의 프로필

jj4783

질문자

2022.08.18

답변 감사드립니다:)

렌더링이 어떠한 순서로 되는지를 알고 모르고의 차이가 큰 것 같습니다!!

0

안녕하세요. 제주코딩베이스캠프입니다.

'http://localhost:8080/todos' 로 적힌 데이터의 구조를 알 수 없어, 다른 api로 테스트를 해보았으나,

별다른 문제를 파악하지 못하였습니다!

'http://localhost:8080/todos' 로 적힌 해당 주소가 올바른지 확인하시고,

맞다면 불러오는 데이터 구조 파악 및 객체의 프로퍼티에 올바르게 접근하고 있는지 확인 부탁드립니다!

     <ul> {lists.data.map(list => <li key={list.id}> ... </li>)} </ul>

기존 교안에서는 lists의 data에 접근하여 출력하는 것이라 확인 후 구조에 맞게 수정해주셔야 할 듯 합니다.

혹시 해당 문제가 해결되지 않으신다면 전체 코드 및 실행환경을 공유해주세요.

감사합니다.

jj4783님의 프로필

jj4783

질문자

2022.08.11

안녕하세요:) 답변 감사드립니다 ㅎㅎ 하루정도 강의를 다시보고 생각해보니 답을 찾았습니다!! ㅎㅎ

제가 생각하고 있는 이 흐름이 맞는지 확인차 다시 답글을 남깁니다.

 

응답값으로 날아오는 형태 역시 배열 안에 객체들이 담긴 형태는 맞았으나, state라는 걸 다시 생각해보니 해결되었습니다.

1. 초기 lists는 빈 배열로 할당이 됩니다.

(const [lists, setLists] = useState([]);로 인해서)

2. useEffect( () => {}, [] )로 인해 첫 렌더링 시 콜백함수를 실행하고, 그 콜백함수 내부에선 요청을 보내고 응답을 받아 lists라는 state를 다시 할당해줍니다.

( ... 생략 ...

const result = await res.json()

setLists(result)

... 생략 ... 로 인해서 )

3. 즉, lists는 [] 이었다가 [{},...,{}]이렇게 재할당 받게 됩니다.

4. 제가 이전에 작성했었던 코드는, 아무런 분기처리 없이 return에서 lists에 바로 접근하고 있었습니다. 즉, 빈배열에 그냥 접근을 하고있었던것 같습니다.

5. 강사님의 코드는 아니지만,

if (lists.data === undefined) { return <h1>로딩중~!~!~!~!!~!~!~!~!~!</h1>}

요런 코드를 임시 추가하니, 아주 잠깐이나마 로딩중이라는 글자가 보였다가 투두리스트가 잘 렌더되었습니다.

위해서 설명하고 있는 흐름이 맞을까요?

추가적으로 질문이 있습니다.

(정적인 정보제공이 아닌 서비스라는 가정하에)거의 모든 웹에선 서버에서 가져온 데이터를 바탕으로 렌더링 해준다고 생각합니다. 이런 구조라면, 컴포넌트의 리턴 문 안에서

-초기에 데이터를 다 불러오지 못했을때

-에러가 났을때

-잘불러와졌을때

위의 3가지 상황을 기본적으로 염두에두고 개발을 하면 될까요??