JS' 공부흔적

[Axios] Axios interceptor와 react-spinners를 사용하여 로딩 중 상태 구현하기 본문

Axios

[Axios] Axios interceptor와 react-spinners를 사용하여 로딩 중 상태 구현하기

이준수 2023. 1. 15. 18:06

Axios에는 인터셉터라는 것이 존재한다. 이는 서버에 요청이 전달되기 전과 서버로부터 응답을 받을 때 가로채서 특정 동작을 수행하도록 하기 위해 존재한다. 인터셉터와 react-spinners 라이브러리를 사용하여 api 요청을 할 때 로딩 중인 상태를 알 수 있도록 로딩바를 구현해보자. 아래 나올 코드는 이전 글과 이어지는 코드이다.

 

[Axios] Axios 인스턴스 생성하기

지금까지 axios를 사용할 때는 아래와 같이 사용했다. const url = "https://jsonplaceholder.typicode.com/"; const getInfo = () => { axios .get(url + "posts") .then((res) => { console.log(res); }) .catch((e) => { console.error(e); }); }; 또

2junsu.tistory.com

 

아래의 코드가 이전 글에 쓰였던 코드인데, 간단히 설명하자면 axios 인스턴스를 생성하고 async await를 사용하여 posts api를 불러온다.

// useAxios.js

export const useAxios = () => {
  const request = axios.create({
    baseURL: "https://jsonplaceholder.typicode.com/",
    timeout: 3000,
  });
  
  return [request];
};
  import { useAxios } from "../hooks/useAxios";
  
  const [request] = useAxios();
  const getInfo = async () => {
    try {
      const response = await request.get("posts");
      console.log(response);
    } catch (e) {
      console.error(e);
    }
  };

 

인터셉터를 사용하기 전에 먼저 위 api를 불러와서 cardList state에 데이터를 저장해서 화면에 뿌려보도록 하겠다.

// Home.jsx

const Home = () => {
  const [request] = useAxios();
  const [cardList, setCardList] = useState([]);

  const getInfo = async () => {
    try {
      const response = await request.get("posts");
      const list = response.data;
      setCardList(list);
    } catch (e) {
      console.error(e);
    }
  };

  return (
    <Container>
      <Btn onClick={getInfo}>GET</Btn>
      <CardContainer>
        {cardList.map((d, i) => (
          <Card
            key={`card-${d.id}`}
            userId={d.userId}
            body={d.body}
            id={d.id}
            title={d.title}
          />
        ))}
      </CardContainer>
    </Container>
  );
};

styled-components를 사용하여 적절히 스타일링한 후, 결과를 보면 아래와 같다. GET 버튼을 누르면 총 100개의 데이터를 불러와서 화면에 뿌린다.

 

SMALL

현재는 버튼을 누르면 바로 데이터가 불러와지며 따로 로딩 중인 지는 알 수 없다. 데이터 수가 적어서 괜찮지만, 데이터가 많아지거나 api 요청이 느려지게 되면 사용자에게 로딩 중이라는 상태를 알려야 한다. 이때 인터셉터가 쓰인다. 인터셉터는 위에서 말했듯이 서버에 요청이 전달되기 전인 request와 서버로부터 응답을 받을 때인 response 상태일 때의 동작을 처리한다. 아래 코드를 보자.

// useAxios.js

export const useAxios = () => {

  ...
  
  const request = axios.create({
    baseURL: "https://jsonplaceholder.typicode.com/",
    timeout: 3000,
  });
  
  	// 요청
    request.interceptors.request.use(
      (config) => requestHandler(config), // 요청이 전달되기 전
      (e) => errorHandler(e) // 요청 오류 발생 시
    );

	// 응답
    request.interceptors.response.use(
      (response) => responseHandler(response), // 응답 성공 시
      (e) => errorHandler(e) // 응답 오류 발생 시
    );

  ...
  
};

이처럼 request라는 인스턴스를 만들고 request.interceptors.request.use와 같이 인터셉터를 사용한다. 이때 request와 response 모두 2개의 콜백함수가 들어간다. 가독성을 위해 requestHandler, responseHandler, errorHandler 함수를 따로 분리했다. requestHandler는 요청이 전달되기 전이므로 이때 로딩 중이라는 걸 알려야 한다. 그리고 responseHandler와 errorHandler는 각각 응답이 성공했을 때와 오류가 발생했을 때이므로 로딩을 멈춰야한다. 이때 로딩 상태를 관리하기 위해서 boolean 타입의 상태를 선언할 것이다. 그러나 우리는 지금 이 로직을 useAxios라는 커스텀 훅으로 분리해놓은 상태이고, 재사용성을 높이기 위해서 로딩 상태를 전역적으로 선언하는 것이 효율적이므로 recoil을 사용하여 관리할 것이다.

// loading.js

import { atom } from "recoil";

export const loadingState = atom({
  key: "loadingState",
  default: false,
});
// useAxios.js

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { useSetRecoilState } from "recoil";
import { loadingState } from "../recoil/atoms/loading";

export const useAxios = () => {
  const setLoading = useSetRecoilState(loadingState); // 로딩 상태를 전역적으로 관리

  // 요청 핸들러
  const requestHandler = (config) => {
    setLoading(true); // 로딩 시작
    return config;
  };

  // 응답 핸들러
  const responseHandler = (response) => {
    setLoading(false); // 로딩을 멈춰야 함
    return response;
  };

  // 에러 핸들러
  const errorHandler = (e) => {
    setLoading(false); // 로딩을 멈춰야 함
    return Promise.reject(e);
  };

  const request = axios.create({
    baseURL: "https://jsonplaceholder.typicode.com/",
    timeout: 3000,
  });

  request.interceptors.request.use(
    (config) => requestHandler(config),
    (e) => errorHandler(e)
  );

  request.interceptors.response.use(
    (response) => responseHandler(response),
    (e) => errorHandler(e)
  );

  return [request];
};

이렇게 로딩 상태 관리까지 끝났다. 이렇게 로딩 상태를 변경하면서 로딩 중이라면 로딩 바를 화면에 띄워야 하고, 그렇지 않으면 로딩 바를 없애야 한다. 이 로딩 바를 위해 react-spinners라는 라이브러리를 설치하여 Loading 컴포넌트를 구현하였다. 참고로 이 링크를 클릭하면 react-spinners에서 사용 가능한 다양한 스피너가 나오는데, 마음에 드는 스피너를 골라서 아래와 같이 작성하면 된다. 나는 PulseLoader를 선택했다.

npm i react-spinners
// Loading.jsx

import React from "react";
import { PulseLoader } from "react-spinners";

const Loading = () => {
  return (
    <div>
      <PulseLoader color="skyblue" />
    </div>
  );
};

export default Loading;
반응형

이렇게 로딩 컴포넌트까지 만든 후에 다시 Home.jsx로 돌아가서 코드를 추가로 작성해보자. 우선 recoil로 관리한 로딩 state를 불러온 후에 state 값에 따라 true이면 로딩 컴포넌트를 보여주면 된다. 따라서 전체 코드는 아래와 같다.

// Home.jsx

import React, { useState } from "react";
import { useRecoilValue } from "recoil";
import styled from "styled-components";
import Card from "../components/Card";
import Loading from "../components/Loading";
import { useAxios } from "../hooks/useAxios";
import { loadingState } from "../recoil/atoms/loading";

const Home = () => {
  const [request] = useAxios();
  const [cardList, setCardList] = useState([]);
  const loading = useRecoilValue(loadingState);

  const getInfo = async () => {
    try {
      const response = await request.get("posts");
      const list = response.data;
      setCardList(list);
    } catch (e) {
      console.error(e);
    }
  };

  return (
    <Container>
      <Btn onClick={getInfo}>GET</Btn>
      {/* 로딩 바 */}
      {loading && <Loading />}
      <CardContainer>
        {cardList.map((d, i) => (
          <Card
            key={`card-${d.id}`}
            userId={d.userId}
            body={d.body}
            id={d.id}
            title={d.title}
          />
        ))}
      </CardContainer>
    </Container>
  );
};

 

결과를 확인해보면 잠깐이지만 로딩 스피너가 보이는 것을 확인할 수 있다!

 

 

728x90
반응형