wafflestudio / seminar-2021

2021 Rookies 세미나
47 stars 110 forks source link

Useeffect로 데이터를 가져올 때 문제가 발생합니다. #542

Closed hmlinnie closed 3 years ago

hmlinnie commented 3 years ago

요약

안녕하세요. 와플 루키 김혜민입니다. 마운트될 때 Useeffect()를 이용해 데이터를 받아 state에 저장하려 시도하고 있으나 오류가 생겨 질문 드립니다.





상황

    const [student, setStudent] = useState();

    useEffect(() => {
        const token = localStorage.getItem('token')
        const fetchStudent = () => {
            axios.get('https://p04fpgjlo3.execute-api.ap-northeast-2.amazonaws.com/v1/student', {
                headers: {
                    Authorization: `Bearer ${token}`
                }
            })
                .then((response) => {
                    setStudent(response.data);
                })
        };
        fetchStudent();
    }, []);





문제 내용

위 코드와 같이 setStudent로 데이터를 저장하고, 이후 student 객체를 사용하고자 하였으나, student 객체에는 아무것도 저장되지 않았다는 오류 메시지가 계속해서 발생합니다.

그래서 여러 가지를 시도하던 중, const [student, setStudent] = useState() 부분에서 useState()initialState를 설정해놓을 경우(가령 더미데이터 저장) 제대로 작동하는 것을 발견했습니다.

왜 이런 현상이 발생하는지, 제대로 작동되기 위해 어떤 부분을 고쳐야하는지 궁금합니다!

woohm402 commented 3 years ago

좋은 질문입니다! 제가 알려드렸어야 했을 거 같네요 😅

요약

잘 구현하셨습니다.

상세

세미나 2 강의 자료리액트 공식문서 useEffect 에 따르면, useEffect는 한 번 렌더가 된 다음 실행됩니다. 따라서 axios를 이용한 fetch가 마무리되기 전까지 무조건 한 틱 이상의 간극이 존재하고, 그 기간 동안은 initialData를 이용해 render하게 됩니다.

굳이 그거 때문이 아니더라도, 생각해보면 서버에서 데이터를 받아올 때까지 시간이 필요합니다. (세미나용 서버 같은 경우에는 워낙 트래픽도 적고 디비도 작아서 그런지 엄청 빠르더라고요. 그래서 잘 안 느껴지셨을 거예요) 보통 비동기 처리에 0.1초 이상이 소요됩니다. 따라서 자연히 그 기간동안은 데이터가 없는 게 맞고, 아무튼 로드가 되기 전까지 initialData를 써서 render하는 게 맞습니다.

그럼 데이터가 없을 땐 어떻게 하느냐? 데이터가 없는 (아마도 로드되기 전) state에서의 뷰를 만들어 주는 게 일반적입니다. 가령 아래와 같은 방법이 있을 것 같네요!

const [studentList, setStudentList] = useState();

useEffect(() => {
  axios.get('/student')
    .then(response => {
      setStudentList(response.data);
    });
}, []);

if (studentList === undefined) return <h1>loading...</h1>

return <StudentListView studentList={studentList} />;

근데 이제 UX를 생각하면 위 같은 방법은 데이터가 로드되기 전까지 아무것도 안 보여주겠다는 건데 좀 별로입니다. 렌더할 때 조건부 렌더링을 써서, studentList === undefined 라면 studentList.map(student => <li> 어쩌구 대신 loading이라는 텍스트를 띄우거나, 이런 로더 gif를 띄우는 게 베스트겠네요!

woohm402 commented 3 years ago

사실 저기서 조금만 더 생각하면, studentList === undefined로 로딩중인 걸 체크하는 건 의미상 그렇게 좋지 않다는 걸 알 수 있습니다. 로딩 끝났는데 토큰이 만료돼서 axios.get 이 에러나서 studentList === undefined가 유지되는 중일 수도 있잖아요?

그런 부분을 고려한다면, 보통 isLoading이라는 state를 하나 더 두는 방식을 채택합니다. axios.get 호출하기 전에 true로 설정하고, .then 에서 false로 설정하는 거죠

const [isLoading, setIsLoading] = useState(false);
const [studentList, setStudentList] = useState();

useEffect(() => {
  setIsLoading(true);
  axios.get('/student')
    .then(response => {
      setStudentList(response.data);
      setIsLoading(false);
    });
    .catch(err => {
       // toast 등으로 에러 보여주고
      setIsLoading(false);
    })
}, []);

if (isLoading) return <h1>loading...</h1>

return <StudentListView studentList={studentList} />;

또 더 정확한 핸들을 하고 싶다면 에러 발생 시 에러가 발생했다는 걸 보여주기 위해 isLoading 옆에 isError라는 state도 추가해서 비슷하게 처리할 수도 있겠습니다만 사실 이번 과제 스펙이랑은 관련이 없기도 하고, 여기서 오류가 날 만한 부분이 토큰 만료밖에 없으니 이번 과제에서는 이 api에서 에러가 났을 경우 바로 로그아웃시켜버리는 방식이 좀더 적합할 것 같네요

woohm402 commented 3 years ago

질문과 관련은 없지만 https://github.com/wafflestudio/19.5-rookies/issues/531 참고하셔서 localStorage 말고 state에서 토큰 가져오도록 변경하시는 걸 추천드립니다!

hmlinnie commented 3 years ago

네!! 알려주신대로 잘 해결했습니다. 자세히 설명해주셔서 감사합니다!!