본문 바로가기
Develope/REACT

[리액트] 비동기 처리 에러로 인한 api 호출 순서 에러

by ccuccu 2024. 9. 1.

리액트에서 api 호출 순서로 인하여 에러가 발생했다.

원래 의도대로 실행이 된다면 api 함수 호출이 완료 된 후 페이지에서 호출 된 값을 따라 화면에 내용이 작성되어야 하는데 화면에 코드 실행이 되지 않았다.

 

기존 코드

// 전달 받은 각 api 정보
    const location = useLocation();
    const item = location.state?.item;
    
// 각각의 작업 리스트 값
    const [workList, setWorkList] = useState([]); // api data 값
    const getModApi = async () => {
        try{
            const url = '/api/work_api/getMod';
            let params = {
                idx: item.work_idx
            }
            const response = await axios.get(url, {
                params: params,
                headers: {
                    'Authorization': `Bearer ${accessToken}`
                }
            });
            setWorkList(response.data.data)
            console.log('함수 내부 ' + workList.work_date)
            // console.log(item)
            // console.log(workList)
            return response;
        }catch(error){
            console.error(error)
        }finally{
            setLoading(false)
        }
    }

    useEffect(() => {
        console.log('함수 호출 전 ' + workList.work_date)
        getModApi()
        console.log('함수 호출 후 ' + workList.work_date)
    }, [])

 

의도한 대로라면 함수 호출 전에는 workList.work_date가 undefined가 뜨는게 맞지만, getModAPi를 호출 한 후에는 해당 값이 들어와야 하는데 둘 다 undefined가 떴다.

 

하지만 특이점이 있었는데, 바로 홈페이지를 새로고침 한 후 파일을 저장하면 콘솔에 값이 찍혔다.

새로 고침한 직후 콘솔창
로드 된 페이지에서 저장 했을 때

 

예상한 이슈 이유

이럴 때엔 높은 확률로 자바스크립트 비동기 처리로 인한 에러 문제였기 때문에, 새로고침과, 내부 에서 다시 리렌더링 될 때의 차이를 찾아보았다.

 

새로고침과 화면 리렌더링의 차이

사실, 새로고침과 렌더링 될 때 차이점은 처음에 react 프로젝트를 아무것도 바꾸지 않고, 있는 코드 그대로 버튼을 클릭해보면 다르다는 것을 알 수 있다. 프로젝트를 실행 시켜서 웹으로 띄워보면, state를 이용한 버튼 클릭 함수를 만나볼 수 있다. count is 버튼을 클릭해보면 주어진 숫자 (예시: 0)에서 숫자 값이 +1이 된다. 하지만 아무리 숫자를 999를 하든 100을 하든 새로고침하면 다시 0으로 돌아온다. 여기서 우린 화면 리렌더링과 새로고침이 다르게 작동한다는 것을 알 수 있다.

 

여기서 리액트와 단순 Html, Css, JS 파일만을 사용하던 과거의 방식으로 인한 차이점을 확인할 수 있다. 쉽게 말하면 리액트는 Virtual DOM과 DOM을 사용하고, 과거 방식은 DOM만을 사용한다. 리액트에서 리렌더링은 Virtual DOM의 변화를 직접적으로 보여주고 DOM과의 비교를 통해 바뀐 부분만 새로 재로드 되지만, 새로고침은 DOM이 재로드 된다고 생각하면 될 것 같다.

그렇다면 왜 Virtual DOM을 사용할까?

개인적으로, 이 질문은 왜 React를 사용하냐란 질문에도 어느 정도 해답을 해줄 수 있다고 생각한다. 기존 DOM만을 직접적으로 화면에 띄우던 예전 방식인, Html, Css, Javascript를 사용하던 웹 사이트는 여러가지 문제를 차치하고 나서도, 무겁고 느리다는 문제가 발생했다.

새로고침 할 때마다 Html, Css, JS 파일을 모두 재 로드해서 서버에서 다시 실행시켰고, 규모가 큰 페이지 일수록 문제는 더욱 도드라지게 보였다. 하지만 Virtual DOM을 사용한다면 위의 과정을 조금 더 가볍고 빠르게 진행할 수 있다.

 

Virtual DOM은 쉬운 예를 들어보자면, DOM의 복사본이다. 웹 사이트 실행 중에 바뀐 점이 있다면, 기존 모든 파일을 계속해서 새로 로드하던 과거의 방식과는 달리 Virtual DOM을 사용 시, 원본인 DOM과 복사본이 Virtual DOM을 비교해서 바뀐 부분만 다시 실행해주면 된다. 모든 파일을 서버에서 다시 리로드 하지 않기 때문에, 훨씬 가볍고 빠르다.

 

해결 시도 1.

다시 돌아와서, 이런 차이점이 나에게 문제 해결의 실마리를 제공했다.

콘솔로는 확인 할 수 없었던, workList가 getModeApi가 실행되는 과정에 setWorkList를 통해 api data를 배열로 담으려고 했던 나의 의도와는 달리, 이 과정이 오래 걸린다고 판단되어 js의 싱글 스레드가 웹 브라우저의 Web APIs에 넘겨버렸기 때문 같았다. 이럴 땐 주로 Async await 함수를 이용하여 과정을 비동기적으로 처리 해주었는데, 

...영향을 주지 않았다. 찾아보니 set은 비동기 적으로 상태를 업데이트 하는건 맞지만 자체적으로 비동기 함수가 아니기 때문에 영향을 받지 못한다고 한다.

 

해결 시도2

그렇다면, workList 값이 변한 후에, getModeApi 함수를 실행 시킨다면 어떻게 될까?

처음 undefined가 뜨는 것은 동일하지만, 순식간에 계속해서 콘솔창에 로그가 나타났다. workList가 변경된 적이 없는데 계속해서 업데이트 되며 콘솔 로그가 실행 되는 것 같았다. 기존 코드와 부딪치고 있는 걸까?

useEffect를 통해 workList가 업데이트 되지 않았는데 계속 실행된다면 불변성이 깨졌을 확률이 높지만, 현재 코드에서는 리스트를 담는 정도밖에 없었기 때문에 다시 감이 오지 않기도 했고, 처음 undefinded가 뜨고, 화면 input에 date 값이 받아지지 않는 것은 똑같았기 때문에 좋은 방법이 아닌 것 같았다.

 

(하지만 왜 아직도 workList로 직접 호출했더니 저렇게 반복됐는지는 잘 모르겠다..)

 

해결 방법

곰곰이 생각해보던 와중에 vsc 저장으로 일어난 리렌더링에는 값이 담긴다는건, 비동기 처리든 뭐든 결국 어느 순간엔 값이 담기는 것일테니 workList에 값이 있다면 내가 직접 value 값들이나 필요한 곳에 값을 다시 담고 리렌더링 하면 되지 않을까? 라는 생각이 들었다.

그리고나서 workList 값이 변경 될 때마다(실은 null 에서 값이 담기는 순간) 호출한다면, 자연스럽게 리렌더링 되면서 값이 호출 되지 않을까? 라는 생각이 들었다.

 

기쁜건 크게!!!

ㅠㅠㅠㅠㅠ 드디어 되었다. 별 것 아니었지만 정말 오래 걸렸고.. 그래도 이것 저것 알아보면서 기초적인 지식이 더 쌓인 것 같아서 뿌듯하다.

이렇게 예상치 못하게 코드가 실행되지 않을 때면, 당황하게 되는 것도 있지만 결과적으론 차근차근 알아가면서 생각하고 하는 과정이 너무 재밌어서 코딩을 계속 하게 되는 것 같다.

 

하단은 최종 코드!!!

 

완성 코드

 const accessToken = localStorage.getItem('accessToken'); // api 인가용 aceessToken값
    const [workList, setWorkList] = useState([]); // api data 값
    const getModApi = async () => {
        try{
            const url = '/api/work_api/getMod';
            let params = {
                idx: item.work_idx
            }
            const response = await axios.get(url, {
                params: params,
                headers: {
                    'Authorization': `Bearer ${accessToken}`
                }
            });
            setWorkList(response.data.data);
            setSubWorkList(response.data.subdata);
        } catch(error) {
            console.error(error);
        } finally {
            setLoading(false);
        }
    }

    useEffect(() => {
        getModApi();
    }, []);

    useEffect(() => {
        if (workList && workList.work_idx) {
            setWorkDate(workList.work_date); // 위에 나온 input date 값
        }
    }, [workList]);