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 ํ›…์˜ ์ฝœ๋ฐฑ์€ snapshot๊ณผ previousSnapshot์„ ์†์„ฑ์œผ๋กœ ๊ฐ€์ง„ ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.

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)

๐ŸŒš Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.