Next.js로 이것저것 (3) - 3. 재사용성 높은 HOC 다이나믹 컴포넌트 만들기 (feat. react-query)

sham·2024년 12월 4일
1

Next.js 제로베이스

목록 보기
6/9
post-thumbnail

개요

컴포넌트에서 API 등을 불러와야 할 때 CSR 방식이라면 데이터를 가져오는 동안의 제어가 필수적이다.

일반적으로는 isLoading등을 useState, useEffect 등으로 관리하면서 isLoading state가 true일 때 데이터를 렌더링하는 방식을 사용하게 된다.

Next.js는 SSR 방식을 사용하기 때문에 isLoading을 고려하지 않고 데이터를 요청해서 렌더링할 수 있다. 하지만 실시간 데이터 요청 같은 경우 CSR의 react-query 등을 사용하는 것이 더 유리할 수 있기에 Next.js에서도 실시간 데이터 요청이 있을 경우 isLoading을 사용하는 방식은 필수적이다.

매번 데이터를 요청하는 컴포넌트마다 isLoading 상태를 지정해주는 것은 번거롭기 때문에, react-query를 사용했을 때 리턴받는 isLoading, error를 이용해서 재사용성이 높은 HOC 다이나믹 컴포넌트를 만들어보자.

본문

app/page.tsx

HOC 컴포넌트를 사용하는 컴포넌트이다. react-query로 데이터, isLoading, error를 리턴받았다.

isLoading, error은 HOC 컴포넌트에 props로 넣고, 원래 렌더링하려는 주목적이 될 컴포넌트에 필요한 데이터들을 props로 넣어주었다.

'use client';

import { APIComponent, Clock } from '@/components';
import { useBitcoinQuery } from '@/queries/useBitcoinQuery';
import CoinChart from './CoinChart';

export default function Home() {
  const { coinArr, time, localTime, isLoading, error } = useBitcoinQuery();

  return (
    <div className='grid min-h-screen grid-rows-[20px_1fr_20px] items-center justify-items-center gap-16 p-8 pb-20 font-[family-name:var(--font-geist-sans)] sm:p-20'>
      <Clock />
      <APIComponent {...{ isLoading, error }}>
        <CoinChart {...{ coinArr, time, localTime }} />
      </APIComponent>
    </div>
  );
}

components/APIComponent.tsx

만약 error 값이 존재한다면 throw로 에러를 던진다. next.js 환경이기에 error.tsx이 캐칭해서 처리할 것이다.

그게 아니라면 isLoading 값에 따라 로딩창이나 본문을 리턴해주면 된다.

interface APIComponentProps {
  isLoading: boolean;
  error: any;
  children: React.ReactNode;
}

export default function APIComponent({ isLoading, error, children }: APIComponentProps) {
  if (error) {
    throw new Error('API 요청 중 에러가 발생했습니다.');
  }
  return <>{isLoading ? <div>로딩 중...</div> : children}</>;
}

app/CoinChart.tsx

'use client';

import { useState, useEffect } from 'react';

import { useBitcoinQueryType } from '@/queries/useBitcoinQuery';
import Select from 'react-select';
import { coinId } from '@/lib';

export default function CoinChart({ coinArr, time, localTime }: useBitcoinQueryType) {
  const [coin, setCoin] = useState('');

  const onChangeCoin = (selectedOption: any) => {
    if (selectedOption) {
      setCoin(selectedOption.value);
    }
  };

  useEffect(() => {
    setCoin(options[0].value);
  }, []);
  const options = Object.keys(coinId).map(key => {
    return {
      value: key,
      label: key,
    };
  });

  return (
    <div>
      최신 데이터 : {localTime}
      선택한 코인 : {coin}
      <Select options={options} defaultValue={options[0]} onChange={onChangeCoin} />
      {coinArr?.map(item => {
        return (
          <div key={item.id}>
            {item.name} : {item.priceUsd}
          </div>
        );
      })}
    </div>
  );
}
profile
씨앗 개발자

0개의 댓글