[Recoil] 리코일로 Axios Hook 구현하기

2020. 7. 11. 15:54JavaScript

최종 코드는 제일 아래에 있습니다!

Axios Hook

Axios Hook은 다음과 같이 사용한다.

const [{ data, loading, error }, refetch] = useAxios(
    'https://some-url.com'
)

응답의 결과가 담기는 data, 통신 진행 여부를 알려주는 loading, 요청의 에러를 담는 error와 요청을 다시 보낼 수 있는 refetch() 메서드가 리턴된다.
리코일에서도 이와 비슷하게 훅처럼 사용할 수 있으면 좋을 것 같아서 몇 가지를 시도해봤다.

Recoli에서는?

먼저 data를 받는 코드는 다음과 같이 간단하게 구현할 수 있다. (참고: 리코일 공식 문서)

const getUserInfo = selector({
  key: 'getUserInfo',
  get: async ({ get }) => {
    const response = await axios.get('https://some-url.com');
    return response.data;
  },
});

const CurrentUserInfo = () => {
  const userInfo = useRecoilValue(getUserInfo);
  return <div>{userInfo}</div>;
}

const MyApp = () => {
  return (
    <RecoilRoot>
      <CurrentUserInfo />
    </RecoilRoot>
  );
}

loadingerror는 리코일에서 제공하는 Loadable 객체를 사용해서 구현할 수 있다.

...

const CurrentUserInfo = () => {
  const userInfoLoadable = useRecoilLoadableValue(getUserInfo);

  switch (userInfoLoadable.state) {
    case 'hasValue':
      return <div>{userInfoLoadable.contents}</div>;
    case 'loading':
      return <div>loading...</div> ;
    case 'hasError':
      throw userInfoLoadable.contents;
  }
};

...

useRecoilLoadableValue 훅은 useAxios 훅의 {data, loading, error}와 유사한 결과를 리턴한다. 이 훅은 두 개의 인터페이스를 다음과 같이 갖는다.

  • state
    • 'hasValue', 'loading', 'hasError' 중 하나의 값을 갖는다. 의미가 매우 직관적이다.
  • contents
    • state가 'hasValue'일 경우, 실제 값을 갖는다.
    • state가 'loading일 경우, 값의Promise`를 갖는다.
    • state가 'hasError'일 경우, Error 객체를 갖는다.

Refetch(Refresh)

아직 이 코드는 컴포넌트가 처음 랜더링 될 때만 axios 요청을 보낸다. 만약 임의로 refresh하고 싶다면 다른 방법으로 구현해야한다. 아직 공식적으로 지원하는 기능이 없는 것으로 알고 있다. 하지만 약간의 편법을 동원한다면 이를 구현할 수 있다.
selector는 구독 중인 atom의 상태가 변할 때마다 값을 새로 계산한다는 점을 이용하는 것이 핵심이다.

const _getUserInfoTrigger = atom({
  key: '_getUserInfoTrigger',
  default: 0,
});

const getUserInfo = selector({
  key: 'getUserInfo',
  get: async ({ get }) => {
    get(_getUserInfoTrigger); // 트리거의 상태를 구독해서 트리거 값이 변경될 때마다 새로 계산
    const response = await axios.get('https://some-url.com');
    return response.data;
  },
  set: ({ set }) => {
    // setter가 호출되면 트리거의 값을 1만큼 증가 
    // => 트리거 값 변경으로 인해 userInfo가 갱신됨
    set(_getUserInfoTrigger, v => v + 1); 
  }
});

const CurrentUserInfo = () => {
  const [userInfoLoadable, refetchUserInfo] = useRecoilLoadableState(getUserInfo);

  switch (userInfoLoadable.state) {
    case 'hasValue':
      return (
        <>
          <div onPress={refetchUserInfo}>리로드</div>
          <div>{userInfoLoadable.contents}</div>;
        <>
      );
    ...
  }
};

위와 같은 방식으로 refetch를 구현할 수 있다.