컴포넌트에서 API 등을 불러와야 할 때 CSR 방식이라면 데이터를 가져오는 동안의 제어가 필수적이다.
일반적으로는 isLoading등을 useState, useEffect 등으로 관리하면서 isLoading state가 true일 때 데이터를 렌더링하는 방식을 사용하게 된다.
Next.js는 SSR 방식을 사용하기 때문에 isLoading을 고려하지 않고 데이터를 요청해서 렌더링할 수 있다. 하지만 실시간 데이터 요청 같은 경우 CSR의 react-query 등을 사용하는 것이 더 유리할 수 있기에 Next.js에서도 실시간 데이터 요청이 있을 경우 isLoading
을 사용하는 방식은 필수적이다.
매번 데이터를 요청하는 컴포넌트마다 isLoading 상태를 지정해주는 것은 번거롭기 때문에, react-query를 사용했을 때 리턴받는 isLoading, error를 이용해서 재사용성이 높은 HOC 다이나믹 컴포넌트를 만들어보자.
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>
);
}
만약 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}</>;
}
'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>
);
}