기존 코드

기존 reduxthunks로 데이터를 가지고 오는 경우 다음과 같이, useEffectparams값이 변할 때마다 데이터를 갱신하도록 처리했습니다.

const handleFetchParticipantList = () => {
  if (procedureId) {
    dispatch(fetchParticipants(companyId, procedureId));
  }
};

useEffect(() => {
  handleFetchParticipantList();
}, [sort, order, status, page, isBookmarked, keyword]);

redux thunks: fetchParticipants를 dispatch하면, 데이터 fetch 후 response를 각각 store에 저장합니다.

export const fetchParticipants =
  (companyId: string, procedureId: string) =>
  async (dispatch: any, getState: any) => {
    const { participants: participantsState } = getState();
    const { params } = participantsState;

    dispatch(stateReset());

    try {
      const response = await getParticipantsByProcedureId(
        companyId,
        procedureId,
        queryString(params),
      );

      const { examinations, totalCount, maxPage } = response;

      dispatch(examinationsFetched({ examinations, totalCount, maxPage }));
    } catch (error: any) {
      dispatch(
        errorOccurred({
          status: error.response.status,
          code: error.response.data.code,
          info: error.response.data.info,
        }),
      );
    }
  };

그러면, 갱신된 redux store 값에 맞춰 해당 페이지가 리렌더링됩니다. 아래 코드에서 useSelector로 가져온 examinations값이 fetchParticipants로 갱신된 데이터입니다.

const { examinations } = useSelector((state: RootState) => state.participants);


문제

여기엔 세가지 문제가 있었습니다.

1. 가독성이 좋지 않음

fetch 시점을 조작하기 위한 비즈니스 로직과 화면을 표현하는 로직과 섞여 가독성이 나빴습니다.

2. 안정성이 떨어짐

SSE로 알람을 받을 때 redux값이 갱신되도록 처리한 부분이 있었는데, SSE가 유실되는 경우 새로운 데이터를 갱신할 수 없었습니다. 그리고, 해당 경우 프론트단이 유실여부를 알 수 있는 방법이 없어 에러핸들링도 할 수 없었습니다.

3. API 관련 데이터 관리가 불편

API 요청시 로딩 여부, 에러, 성공 여부 등 관련 데이터를 따로 리덕스로 관리해줘야 했습니다.


해결 ( swr hook 만들기 )

swr 훅을 만들어서 fetch와 관련된 비즈니스 로직을 모듈화 했습니다.

export const useGetExaminations = ({
  companyId,
  procedureId,
  options,
}: {
  companyId: string;
  procedureId?: string;
  options?: any;
}) => {
  const API_KEY = `/api/v1/participants?companyId=${companyId}&procedureId=${procedureId}`;
  const { params } = useSelector((state: RootState) => state.participants);
  const { t } = useLocalization();

  const getExamination = async (url: string) => {
    try {
      const { data } = await axiosInstance.get(url);
      return data;
    } catch (error: any) {
      if (error?.response.message) {
        Toast.show(t(error.response.message), ToastType.Fail);
        return;
      }
    }
  };

  const { data, mutate, error, isLoading } = useSWR(
    API_KEY + queryString(params),
    procedureId ? getExamination : null,
    options && { ...options },
  );

  return { data, mutate, error, isLoading };
};
//페이지에서 해당 데이터 사용시
const { data: examinations, isLoading } = useGetExaminations();

따라서 위 문제들을 다음과 같이 해결할 수 있었습니다.

1. 가독성이 좋지 않음

-> swr훅으로 비즈니스 로직 분리했기에, 화면 로직엔 swr으로 반환한 데이터만 표시

2. 안정성이 떨어짐

-> SSE가 유실되더라도, swr은 사용자의 focus를 감지해 revalidate할 수 있습니다.

3. API 관련 데이터 관리가 불편

-> swr의 useSWR은 해당 데이터를 반환해주기에, 따로 프론트단에서 값을 관리할 필요가 없습니다.

댓글남기기