ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 여러 API 결과 한꺼번에 fetch해서 보여주기
    Frontend/swr 2024. 9. 8. 22:45

    여러 API 결과를 바탕으로 데이터를 표현해야할 때가 있다. 

     

    내가 작업하는 앱의 경우, 여러 투자 자산 상품들을 보여주고 그에 맞는 총 투자액과 총 투자 이익을 보여주어야 했다.

     

    API로 데이터를 나타내는 방법은 여러가지가 있겠지만, SWR을 활용하여 나타내는 방법을 알아보겠다.

    import useSWR from 'swr'
     
    function Profile () {
      const { data, error, isLoading } = useSWR('/api/user/123', fetcher)
     
      if (error) return <div>failed to load</div>
      if (isLoading) return <div>loading...</div>
     
      // 데이터 렌더링
      return <div>hello {data.name}!</div>
    }

     

     

    아래는 SWR을 활용한 기본 골자이다.

    data를 통해 api의 결과물을 받아오고, error는 throw된 에러를 catch하고, isLoading은 첫 로드를 포함한 여러 데이터가 패칭될 때의 로딩상태를 의미한다.

     

    위를 활용하여 "팜스테이킹"이라는 투자상품에 대한 3개의 API를 가져와 데이터를 보여주는 부분을 완성해보고자한다. 

     

    3개의 API는 각각 다음과 같다.

    1. 투자 정보 보여주기 (getPalmStakingInfo)

    2. 내가 투자한 금액 보여주기 (getPalmStakedAmount)

    3. 투자한 금액에 따른 이자 보여주기 (getPalmStakingClaimable)

     

    3개가 분리된 이유는 아래와 같다.

    1. 투자 정보 fetcher는 유저의 address와 상관없이 같다.

    2. 내가 투자한 금액과 투자 금액에 따른 이자는 address에 따라 다르게 보여주어야 한다.

    3. 내가 투자한 금액만 필요할 때도 있고 투자한 금액에 따른 이자만 필요할 때가 있다. 해당 훅은 잦은 주기(약 12초)마다 호출되기 때문에 항상 두 값을 불러오기보다 필요한 정보만 불러오는 것이 더 좋다. 

     

    그래서 아래와 같이 훅을 각각 정의한다.

    각각의 훅은 각자의 업데이트 주기를 따르며, 렌더링으로 인한 깜빡임을 방지하기 위해 keepPreviousData를 써준다.

     

    이 때, 몇몇 코드에선 onError 처리를 해주었다. 

    onError(undefined, false)는  fetcher의 결과를 undefined로 하고 리벨리데이팅을 하지 않도록 하기 위함이다.

    인터벌하게 fetch하다가 어떤 경우에 데이터 불러오기에 실패할 수 있다. 그래도 swr에서 keepPreviousData를 하게 되면 값이 그대로 보존된다. 그럴 때 이전 데이터 캐시를 지우게 하기 위한 옵션이다. 

     

    아래와 같이 각각의 hook을 모두 불러놓고, 필요한 값을 변수로 선언하면 된다.

     

     // 팜스테이크 정보 불러오기
     const {
        data: palmStakingInfo,
        mutate: refreshPalmStakingInfo,
        error,
      } = useSWR(
        isFocused ? ["palm-staking-info", chainID, blockNumber] : null,
        async ([, _chainID, _blockNumber]) => {
          if (!_chainID || !_blockNumber) return undefined;
          const res = await EmailController.getPalmStakingInfo();
          if (!res || res.code !== APIResponseCode.SUCCESS || !res.data) {
            return null;
          }
          return res.data;
        },
        {
          keepPreviousData: true,
        }
      );
      
     // 팜스테이크 물량 불러오기
    const {
        data: amount,
        mutate,
        error,
      } = useSWR(
        isFocused ? ["palm-staked-amount", chainID, blockNumber, address] : null,
        async ([, _chainID, _blockNumber, _address]) => {
          if (!_address || !_blockNumber || !_chainID) return undefined;
          return PalmStakingController.getStakedAmount(_address);
        },
        {
          keepPreviousData: true,
          onError: () => {
            mutate(undefined, false);
          },
        }
      );
      
      // 팜스테이킹 이자 불러오기
     const {
        data: claimable,
        mutate,
        error,
      } = useSWR(
        isFocused ? ["palm-claimable", chainID, blockNumber, address] : null,
        ([, _chainID, _blockNumber, _address]) => {
          if (!_address || !_chainID || !_blockNumber) return undefined;
          return PalmStakingController.getClaimableAmount(_address);
        },
        {
          keepPreviousData: true,
          onError: () => {
            mutate(undefined, false);
          },
        }
      );

     

     

    필요한 API는 모두 불러왔고, 위 API를 그릴 때 중요한 점이 있다.

    1. 모두 비동기 결과값이니 모두 불러와지고나서 데이터를 로드하고 싶다.

    2. 셋 중 하나라도 에러면 값이 보이지 않아야 한다. 

     

      const totalInvestment = (() => {
        let investment = ZERO_BI;
    
        if (palmStakingAmount) {
          investment += palmStakingAmount;
        }
        if (claimable) {
          investment += claimable;
        }
    
        return investment;
      })();
    
      const totalIncome = (() => {
        let income = ZERO_BI;
        if (claimable) {
          income += claimable;
        }
        return income;
      })();
    
      const isLoading =
        palmStakingAmountInitialLoad ||
        palmStakingInfoInitialLoad ||
        palmClaimableInitialLoad;
    
      const palmStakingError =
        palmStakingAmountError || palmStakingInfoError || palmClaimableError;
        
      const refreshPalmStaking = () => {
        refreshPalmStakingInfo();
        refreshPalmStakingAmount();
        refreshPalmClaimable();
      };

     

    위와 같이 변수들을 세팅하면 필요한 값이 불러와졌을 때 업데이트될 수 있다. 

    코드의 가독성도 높고 해석도 쉽다. 

     

    위 변수들을 활용하여 이제 마지막 렌더링을 해주면 된다. 

     

    export function Investments() {
      const {
        totalInvestment,
        totalIncome,
        palmStakingError,
        palmStakingInfo,
        claimable,
        palmStakingAmount,
        refreshPalmStaking,
        isLoading,
      } = useInvestments();
    
      if (isLoading) {
        return <Loading />;
      }
      
      if (palmStakingError) {
      	return <FailToLoad refresh={refreshPalmStaking} />
      }
    
      return (
        <PalmStaking totalInvestment={totalInvestment} totalIncome={totalIncome} claimable={claimable} palmStakingAmount={palmStakingAmount} />
      );
    }

     

    이렇게 관리하면 loading, error, data를 가독성 있게 관리할 수 있다.

    여기에서 3개의 API 결과값들을 각각에 따라 컴포넌트화해주어 prop을 넉미녀 각자의 업데이트주기대로 리렌더가 되기때문에 부하를 줄일 수 있다. 

     

    코드 상으로는 생략! 

Designed by Tistory.