본문 바로가기

프로젝트

useApi훅에 react-query활용하여 caching기능 넣어보기(1)

사용하고 있던 기존의 useApi훅 소개

문제점 : 너무 많은 Api요청을 해야 한다는 것. 

   - 이전 useApi를 사용했던 프로젝트와 다른 점

   - 이번 프로젝트 특징

계획  및 실제 구현 해보기

 

 

사용하고 있던 기존의 useApi훅


 

더보기
import { useEffect, useState, useCallback } from 'react';
import { API_FETCHER, ApiMethods } from '@utils/axiosConfig';
import { useErrorBoundary } from 'react-error-boundary';
import { AxiosError, AxiosResponse } from 'axios';
import axios from 'axios';

interface UseApiParams {
  method?: ApiMethods;
  path?: string;
  data?: any;
  shouldInitFetch?: boolean;
  initialResult?: string;
}
interface TriggerPropsType
  extends Omit<UseApiParams, 'shouldInitFetch' | 'initialResult'> {
  applyResult?: boolean;
  isShowBoundary?: boolean;
}

type TriggerType = ({
  ...props
}: TriggerPropsType) => Promise<AxiosResponse<any, any>>;

const useApi = ({
  method = 'get',
  path = '',
  data = {},
  shouldInitFetch = false,
  initialResult = '',
}: UseApiParams) => {
  const [result, setResult] = useState(initialResult);
  const [loading, setLoading] = useState(false);
  const [reqIdentifier, setReqIdentifier] = useState('');
  const [error, setError] = useState({});
  const { showBoundary } = useErrorBoundary();

  const trigger: TriggerType = useCallback(
    async ({
      method: triggerMethod = method,
      path: triggerPath = path,
      data: triggerData = data,
      applyResult = true,
      isShowBoundary = true,
    }) => {
      setLoading(true);
      setReqIdentifier(triggerMethod + 'Data');
      try {
        const triggerResult = await API_FETCHER[triggerMethod as ApiMethods](
          triggerPath,
          triggerData
        );

        if (applyResult) {
          console.log('result를 apply합니다');
          setResult(triggerResult);
          return;
        }
        return triggerResult;
      } catch (err) {
        console.log(err);
        if (axios.isAxiosError(err) && isShowBoundary) {
          //에러 바운더리를 보여줘야 할때만 보여줌
          showBoundary(err);
          return;
        }
        axios.isAxiosError(err) && setError(err);
        return;
      } finally {
        setLoading(false);
      }
    },
    [data, method, showBoundary, path, result]
  );

  useEffect(() => {
    shouldInitFetch && console.log('초기 요청합니다!!', method, path);
    shouldInitFetch && trigger({ method, path, data });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return { result, loading, reqIdentifier, trigger, error };
};
export default useApi;

 

이 자체만으로도 다양한 기능을 넣었다고 생각했지만, 

이번 프로젝트를 시작하면서 약간 수정해야 할 필요성을 느꼈다. 

 

 

문제점 : 너무 많은 Api요청을 해야 한다는 것.


 

- 이전 useApi를 사용했던 프로젝트와 다른 점

장바구니에서 수량이나 체크여부를 저장하는 단순한 정보였기 때문에 

useDebouncing으로 가장 나중에 요청한 것만 

실제 api요청을 보내게 만들어도 되었었다.

(이건 왜 api요청을 해야하냐는 이유는 여기에 있다

https://wha-haha.tistory.com/222)

 

 

 

 

- 이번 프로젝트 특징

그러나 이번에는 사용자가 일별로 클릭하는데, 주차별 캘린더가 있어서

해당 일자를 클릭할 때마다 계속 api 요청을 받아와야 했다. 

 

 

 

그렇다면 useApi를 수정해서 caching 기능을 넣어보자!

 

 

계획 및 실제 구현 해보기


react-query를 활용하여 

useApi에 메서드별로

get은 useQuery에서 return하는 값을, 

나머지 메서드는 useMutation에서 return하는 값을 받아와서 

기존의 단순한 사용성은 유지하고

캐싱기능만 추가하도록 한다.

 

1. react query설치

2. useMutationggu.ts 파일 작성

3. useQueryggu.ts 파일 작성 

4. useApi를 변형한 useCachingApi파일 작성 

 

 

 

1. react-query설치 

 

  • 설치
npm i react-query

 

  •  QueryClientProvider 제공
root.render(
  <ErrorBoundary fallback={<Error />}>
    <QueryClientProvider client={queryClient}>
      <ToastProvider>
        <BrowserRouter>
          <Provider store={store}>
            <App />
          </Provider>
        </BrowserRouter>
      </ToastProvider>
    </QueryClientProvider>
  </ErrorBoundary>
);

 

  • QueryClient 설정 
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 20000
    }
  }
});

 

staleTime을 얼마나 할지는 다시 고려 해보자 

        retry: 1,
        retryDelay: 0,
        onError,

 

위의 세 옵션도 생각해보기
retry : 실패한 쿼리 자동으로 재시도 횟수

retryDelay:몇분 후 재시도 할지 

 

 

[트러블슈팅] react-query useMutation onSuccess 안 되는줄 알았던 바보 여기있어요!

라이브러리를 알고 쓰자 / 코드는 거짓말하지 않는다

velog.io

 

 

 

useApi의 매개변수

1. method: method값에 따라 useMutation vs useQuery사용 여부 

2. path: 요청시에 전달 

3. data: useMutation 요청시에 전달 

4. shouldInitFetch: useEffect로 기존의 코드 활용 ㅇ

5. InitialResult: 넣어준 이유는 초깃값이 undefined일때 화면을 렌더링 함으로써 에러가 났기 때문.

기존과 동일하게 result를 return 해줄 때 initialValue를 기본값으로 넣어주면 될듯 ....?

 

 

 

useMutation

필요성:

캐싱기능이 들어가게 되면 무조건 캐싱이 되어버리므로, 

데이터가 변경되었을때 수정 작업을 해야함. 

api요청은 무조건 useCachingApi를 사용하므로 

method에따라 useMutation이냐 useQuery냐는것은 useCachingApi에서 분기해주어야 함.

 

ex:

기록 조회할때는 useQuery로 캐싱된 값을 가져오고 

기록을 수정, 삭제, 생성할 때 새로운 값을 캐싱해야함.

 

 

const { data, isLoading, mutate, mutateAsync } = useMutation(mutationFn, options);

 


useMutationggu 훅 

itemId를 받아서 useMutation을 return해줌

 

 

options 고려해야할것 

1. onMutate : mutation이 실행되기 전 먼저 실행

 => (요청시 바로 UI에 반영하기 위해 기존 query를 가져오고 즉시 업데이트)

2. onSuccess : mutation이 성공하면 실행 (staleTime을 지정해야 성공시 InvalidateQueries 요청이 유효해질 수 있음)

 => InvalidateQueries요청! (itemId값으로)

3. onError : mutation 과정에서 에러가 발생되면 실행

 => 에러문구 띄워주고 확인시 다시 요청... 

4. onSettled: 성공/에러에따라 해당 데이터 전달 받음

 

return값

data: 

mutate: mutate 를 호출해서 mutation 을 실행시킬 수 있다.
variables 는 mutationFn 에 전달하는 객체이다.
onSuccess, onSettled, onError 는 useMutation option에 설명한 것과 동일하다

 

isLoading, 

mutate, 

mutateAsync 

 

 

InvalidateQueries : 상태에 대한 정보가 변경되었을 때 캐싱된 데이터 변경

 

 

react-query 사용시 invalidateQueries를 이용해 캐싱 값 refetch하기

invalidateQueries로 데이터 업데이트하기

velog.io