사소한 삽질 - useEffect

최재홍·2023년 5월 4일
0
post-custom-banner

문제상황

useEffect에 대한 기존에 가지고 있던 지식은 다음과 같았다.

  1. useEffect는 리액트의 대표적인 hook중에 하나이다.
  2. useEffect 내부에 선언된 함수는 컴포넌트가 처음 랜더링 될 때만 실행된다. 그 외에 컴포넌트 내의 state나 props의 변화가 일어나도 재실행되지 않는다.
  3. 특정 시점에 실행시키고 싶을 때는 의존성 배열(dependency array)에 변수를 넣어놓으면 해당 변수가 변화할 때마다 재실행된다.

이러한 지식을 바탕으로 다음과 같은 코드를 작성하였다.

  // 토큰이 있으면 로그인 페이지에 머물지 못 함
   useEffect(async () => {
    try {
      const result = await confirm(localStorage.getItem("access_token"));
      result.status === 200 && navigate("/Main");
    } catch (error) {
      console.log(localStorage.getItem("access_token"));
    }
  }, []);

이 함수는 페이지가 처음 랜더링 될 때, 토큰을 담아서 서버에 요청하여 자신이 가입된 유저가 맞다면 로그인 페이지에서 강제적으로 리다이렉팅하는 함수이다. 여기서 confirm은 외부에서 import한 api이다. 로컬스토리지에 보관하고 있는 access token으로 요청을 해서 가입된 유저가 맞는지 판별하는 api이다. 해당 api는 비동기함수이기 때문에 asynch await으로 호출해야 한다고 생각했다.

그런데 이렇게 실행시키니 오류가 발생하였다. async함수는 언제나 Promise 객체를 반환하기 때문이었다. useEffect의 반환값은 언제나 함수여야 한다. 그렇기 때문에 "destroy is not a function"와 같은 에러 문구를 발생시켰다.

그래서 단순한 나는 다음과 같이 개선했다.

useEffect(() => {
    async () => {
      try {
        const result = await confirm(localStorage.getItem("access_token"));
        result.status === 200 && navigate("/Main");
      } catch (error) {
        console.log(localStorage.getItem("access_token"));
      }
    };
  }, []);

useEffect에 대한 첫번째 인자로 promise객체를 두는 것이 아니라 promise객체를 반환하는 함수를 두는 것이다. 이번에는 "Expected an assignment or function call and instead saw an expression"라는 에러 문구가 발생하였다. 정확한 의미는 알 수 없지만 중괄호 내부에 return이 없을 때 발생하는 에러라고 한다.

그래서 단순한 나는 다시 한번 다음과 같이 개선했다.

useEffect(() => {
    return async () => {
      try {
        const result = await confirm(localStorage.getItem("access_token"));
        console.log(result)
        result.status === 200 && navigate("/Main");
      } catch (error) {
        console.log(localStorage.getItem("access_token"));
      }
    };
  }, []);

"이번엔 확실히 return까지 넣었으니 됐겠지?"라고 생각했는데 result의 값이 vscode상에서 저장을 거듭하거나 할 때, 한박자씩 느리게 console에 찍혔다. 그 이유가 useEffect에 return부는 컴포넌트가 언마운트 될 때 실행된다고 한다.

"useEffect는 항상 함수를 반환해야 합니다.
이 함수는 useEffect훅이 unmount되거나, 다음 useEffect 호출 전에 호출됩니다.
useEffect 훅 내부에서 반환하는 함수는 효과(effect)를 정리(cleanup)하고, 이전에 수행한 작업을 해제하는데 사용됩니다."

그래서 화면이 최초에 랜더링 될 때가 아닌 리랜더링 될 때 함수가 적용됐던 것이다.

해결방안

 useEffect(() => {
    (async () => {
      try {
        const result = await confirm(localStorage.getItem("access_token"));
        result.status === 200 && navigate("/Main");
      } catch (error) {
        console.log(localStorage.getItem("access_token"));
      }
    })();
  }, []);

useEffect 내부에서 당장 실행되어야 하는 부분을 즉시실행함수로 실행시키는 것으로 해결했다. 이렇게 하면 일반적인 useEffect의 실행 시점과 달리 즉시 실행하게 된다. 그래서 우선적으로 토큰 보유 여부를 파악하고, 조건에 따른 함수가 실행되게 되는 것이다.

더 생각해보기

그래서 애초에 useEffect 내부에서 async/await를 사용하는 등의 비동기 작업을 수행하는 것은 권장되지 않는다. 비동기 작업은 promise객체를 반환할 가능성이 있기 때문이다.

이러한 경우에는 useEffect hook 내부에서 async/await를 사용하는 대신 promise를 반환하는 함수를 정의 하고 이를 useEffect 훅 내부에서 호출하는 것이 일반적으로 더 좋은 방법이다.

const checkAccess = async () => {
  try {
    const result = await confirm(localStorage.getItem("access_token"));
    result.status === 200 && navigate("/Main");
  } catch (error) {
    console.log(localStorage.getItem("access_token"));
  }
};

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

이 경우엔 굳이 비동기 통신을 useEffect 내부에서 실행할 필요는 없었다고 한다;;;;

post-custom-banner

0개의 댓글