React Strict Mode에서 API 사용하기

React 18 부터 Stric Mode development 환경에서는 컴포넌트가 두번 렌더링됩니다. 정확히 말하면 mount → unmount → remount의 lifecycle로 이루어집니다. (공식문서)

모든 컴포넌트들에 대해서 unmount한 후 remount 되는 상황을 검사하기 위함입니다. 그래서 useEffect로 lifecycle 로직을 아래와 같이 구현한다면, mount 로직 → unmount 로직 → mount 로직이 수행됩니다.

useEffect(() => {
  // mount 로직
  return () => {
    // unmount 로직
  };
}, []);

이때 mount 로직 부분에 외부 API 콜이 있을 때, 중복해서 불리면 안되는 경우 오류가 발생합니다. 회사에서 겪은 문제로 aws cognito에서 /oauth2/token을 다음과 같이 사용합니다.

useEffect(() => {
  // mount 로직
  const fetch = async () => {
    await axios.post(`${COGNITO_DOMAIN}/oauth2/token`, ...);
  }
  fetch().catch();
  return () => {
    // unmount 로직
  }
}, []);

테스트를 해보면 "invalid_grant" 오류 발생합니다.

aws cognito /oauth2/token 엔드포인트 문서에 정리되어있는 invalid_grant 내용을 보면 다음과 같습니다.

새로 고침 토큰이 취소되었습니다. 권한 부여 코드를 이미 사용했거나 해당 코드가 존재하지 않습니다.

같은 authorization_code를 사용하여 두 번의 api 콜을 하였기 때문이라고 생각합니다. “권한 부여 코드를 이미 사용했거나…” 이 구절에 대한 해석인데, 정확한 해석인지 한 번더 고민해봐야겠습니다.

해결 방법 1: Strict Mode 안 쓰기

별로 좋지 않습니다. Strict Mode는 컴포넌트의 생명주기 검사를 해주기 때문에 안정적인 개발에 매우 유용합니다.

해결 방법 2: 요청 취소

axios 취소 토큰을 사용하여 요청 취소를 할 수 있습니다. 컴포넌트가 unmount될 때 처음 요청을 cleanup 부분에서 취소하는 것입니다. 취소 토큰에 대한 내용은 공식 가이드로 쉽게 이해할 수 있었습니다. 이 때 주의할 점이 하나의 취소 토큰을 모두 요청의 cancelToken으로 지정하면 처음 요청과 remount시 요청이 모두 취소됩니다. 따라서 useEffect 내부에서 취소 토큰을 선언하여 요청마다 다른 취소 토큰을 사용하도록 해주어야 합니다.

useEffect(() => {
  const source = axios.CancelToken.source();
  // mount 로직
  const fetch = async () => {
    await axios.post(`${COGNITO_DOMAIN}/oauth2/token`, {
        cancelToken: source.token
      });
  }
  fetch().catch((thrown) => {
    if (axios.isCancel(thrown) {
        // 의도된 cancel error
      } else {
        // 의도하지 않은 error
      }
  });
  return () => {
    // unmount 로직
      source.cancel();
  }
}, []);

취소된 요청은 cancel error를 throw합니다. 따라서 try catch와 axios의 isCancel을 사용하여 해당 부분의 cancel error를 처리해줍니다.

취소가 정상적으로 동작하면 Network에서 canceled를 확인할 수 있습니다. fetch를 사용하는 경우, AbortController를 사용하여 똑같이 구현할 수 있다고 합니다.

참고

https://hmos.dev/how-to-cancel-at-axios

고민

axios 요청 취소 가이드를 보면 다음과 같이 적혀있습니다.

Axios의 취소 토큰 API는 중단된 proposal-cancelable-promises을 기반으로 하고 있습니다.

“중단된” 것을 기반으로 동작한다니 굉장히 불안한 내용입니다. axios에서도 fetch와 같이 AbortController를 사용하여 처리하는 부분도 있습니다. 위 내용을 좀 더 조사해서 어떤 것을 사용해야할지 고민해봐야겠습니다.