DEV Community

leo
leo

Posted on

Handling error with recoil and axios

Axios

api 호출을 할 때마다 try and catch 를 사용해서 catch 구문에서 에러 핸들링을 하는 방법은 굉장히 번거로운 방법이다. axios의 interceptors 는 이름 그대로 then이나 catch로 응답이나 에러가 오는걸 가로챌 수가 있어서 에러 핸들링을 굉장히 편리하게 할 수가 있다.

  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>;
    response: AxiosInterceptorManager<AxiosResponse>;
  };
Enter fullscreen mode Exit fullscreen mode

axios의 interceptors는 request 속성과 response 속성을 가지고 있는 객체이다. response의 타입을 보면 AxiosResponse를 제너릭 인자로 받는 AxiosInterceptorManager 객체이다.

interface AxiosInterceptorManager<V> {
  use(onFulfilled?: (value: V) => V | Promise<V>, onRejected?: (error: any) => any): number;
  eject(id: number): void;
}
Enter fullscreen mode Exit fullscreen mode

response의 use 메소드는 두 개의 함수를 인자로 받는다. 첫 번째 함수는 요청이 성공적이면 실행이 되고, 두번째 함수는 요청이 실패 했을 때 받는 함수로 에러가 나게 되면 이 함수가 실행된다.

 export const setupInterceptors = (): void => {
  axios.interceptors.response.use(
    (response) => response,
    (error) => {
      --에러처리를 하는구간--
    },
  );
};

Enter fullscreen mode Exit fullscreen mode

setupInterceptors를 함수를 만들어서, 리엑트의 가상돔을 렌더링 해주는 index.tsx 파일에서 import를 해서 실행을 해준다.

이렇게 되면, 리엑트의 어느 api 호출하는 함수를 실행을 했는데 에러가 났다면, 컴포넌트에 있는 catch 구문에 에러가 전달되기 전에 미리 intercepts를 통해서 에러 핸들링을 할 수가 있다.

import React from 'react';
import ReactDOM from 'react-dom';
import { RecoilRoot } from 'recoil';
import { setupInterceptors } from '@service/axios';

import App from './App';

setupInterceptors();

ReactDOM.render(
  <RecoilRoot>
    <App />
   </RecoilRoot>,
   document.getElementById('root'),
);

Enter fullscreen mode Exit fullscreen mode

Change recoil state outside of React

1. RecoilExternalStatePortal

axios로 에러 핸들링을 하기 위해서는, 리엑트 바깥에서 recoil 스테이트를 업데이트해 주어야 한다. 이 부분을 해결하기 아래 링크에서 힌트를 얻어서 해결하게 됐다.

https://github.com/facebookexperimental/Recoil/issues/289

recoil은 redux처럼 Provider의 store를 통해서 state를 업데이트 할 수 있는 방법이 없기 때문에, RecoilExternalStatePortal(jsx를 리턴하는 함수)를 생성을 해서 안에 넣어준다. 이렇게 되면 RecoilExternalStatePortal 함수 안에서 recoil 훅들을 사용할 수 있다.

ReactDOM.render(
  <RecoilRoot>
    <RecoilExternalStatePortal />
      <App />
   </RecoilRoot>,
   document.getElementById('root'),
);
Enter fullscreen mode Exit fullscreen mode

2. externalRecoilLoadable

export let externalRecoilLoadable: <T>(recoilValue: RecoilValue<T>) => Loadable<T> = null;

export let externalRecoilSet: <T>(recoilVal: RecoilState<T>, valOrUpdater: ((currVal: T) => T) | T) => void = null;

export function RecoilExternalStatePortal() {
  useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
    externalRecoilLoadable = snapshot.getLoadable;
  });

  useRecoilCallback(({ set }) => () => {
    externalRecoilSet = set;
  })();

  return <></>;

Enter fullscreen mode Exit fullscreen mode

useRecoilTransactionObserver_UNSTABLE 훅의 콜백은 snapshotpreviousSnapshot을 속성으로 가진 객체를 인자로 받는다.

function useRecoilTransactionObserver_UNSTABLE(({
  snapshot: Snapshot,
  previousSnapshot: Snapshot,
}) => void)
Enter fullscreen mode Exit fullscreen mode

Snapshot은 객체이며 getLoadable 이라는 메소드를 가지고 있다. getLoadable의 타입을 살펴보자.

 getLoadable<T>(recoilValue: RecoilValue<T>): Loadable<T>;
Enter fullscreen mode Exit fullscreen mode

useRecoilTransactionObserver_UNSTABLE의 콜백은 최초 실행이 될때
externalRecoilLoadable 변수안에 스냅샷의 getLoadable 함수를 넣어준다. 이 콜백함수같은 리코일의 스테이트가 바뀔때마다 실행이 되기 때문에, 바뀐 스테이트에 대한 스냅샷이 계속 업데이트가 된다.

getLoadable은 Loadable이라는 객체를 리턴을 해준다. Loadabl 안에는 getValue라는 메소드가 있는데 이 메소드를 통해서 리코일 스테이트값에 접근을 할수가 있다.

useRecoilTransactionObserver_UNSTABLE의 콜백은 RecoilExternalStatePortal 실행이 될 때 최초 실행이 된다.

externalRecoilLoadable 변수안에 스냅샷의 getLoadable 함수를 넣어준다. 이 콜백함수는 리코일의 스테이트가 바뀔 때마다 실행이 되기 때문에, 바뀐 스테이트에 대한 스냅샷이 계속 업데이트가 된다.

3. externalRecoilSet

externalRecoilLoadable로 스테이트에 대한 접근을 할 수는 있지만, 아직 Recoil의 스테이트를 업데이트 할 수는 없다. 따라서 스테이트를 업데이트 할 수 있는 함수가 필요하다.

type CallbackInterface = {
  snapshot: Snapshot,
  gotoSnapshot: Snapshot => void,
  set: <T>(RecoilState<T>, (T => T) | T) => void,
  reset: <T>(RecoilState<T>) => void,
};

function useRecoilCallback<Args, ReturnValue>(
  callback: CallbackInterface => (...Args) => ReturnValue,
  deps?: $ReadOnlyArray<mixed>,
): (...Args) => ReturnValue
Enter fullscreen mode Exit fullscreen mode

위의 있는 useRecoilCallbac은 유저가 넣은 콜백 함수를 실행하는 함수를 반환해준다. 유저의 콜백 함수를 감싸는 wrapper 함수가 CallbackInterface를 전달해줘서. 여기서 set 함수를 인자로 받는다.

콜백 함수 안에서 인자로 받은 set 함수를 externalRecoilSet 변수에 넣어준다. 이 set 함수는 첫 번째 인자로 리코일 상태 값을 받고, 두번째 인자로 업데이트할 상태 값을 넣어 줄 수가 있다.

useRecoilCallbac 에서 반환된 함수가 아직 실행되지 않았기 때문에 set 함수를 넣어주기 위해서는useRecoilCallbac을 실행해준다. 이렇게 되면 externalRecoilSet 변수에 set 함수를 넣어줄 수 있다.

에러핸들링

import { modalState } from '@state/modal';
import { externalRecoilLoadable, externalRecoilSet } from '../RecoilExternalStatePortal';

 export const setupInterceptors = (): void => {
  axios.interceptors.response.use(
    (response) => response,
    (error) => {
      const currentModalList = [...externalRecoilLoadable(modalState).getValue()];
      const newModalList = currentModalList.concat([
        {
          key: 'basicModal',
          props: { text: error.response.data.message }
        },
      ]);
      externalRecoilSet(modalState, newModalList);
    },
  );
};

Enter fullscreen mode Exit fullscreen mode
  • RecoilExternalStatePortal 에서 externalRecoilLoadable 함수와 externalRecoilSet 함수를 임포트 해보자.
  • externalRecoilLoadable의 getValue를 통해서 recoil의 스테이트 값을 가져온다.
  • externalRecoilSet에 첫 번째 인자로 recoil 스테이트 갑을 넣어주고, 두 번째 인자로 업데이트할 valu값을 넣어서 업데이트 했다.

위의 예시 같은 경우는, 에러가 일어나면 interceptors에서 에러를 먼저 처리해서 에러로 받은 메시지 값을 보여주기 일괄적으로 보여주기 위해서 위와 같이 사용되었다.

Top comments (0)