비동기 작업(API 호출)은 여러 가능한 결과를 가지므로, 데이터 그 이상을 모델링해야 합니다 — 로딩, 성공, 에러 state를 표현해야 합니다. 이를 잘 모델링하면 오래된 데이터를 보여주거나 피드백이 없는 등의 UI 버그를 막을 수 있습니다.
단순한 접근과 그 결함
js
[data, setData] = ();
[loading, setLoading] = ();
[error, setError] = ();
독립적인 플래그는 불가능해야 할 모순된 조합(로딩 그리고 에러 그리고 데이터가 동시에)을 허용합니다 — 혼란스러운 UI 버그의 원천입니다.
// ✅ 단일 `status`가 state를 상호 배타적으로 만듦
const [state, setState] = useState({ status: "idle", data: null, error: null });
// status는 다음 중 하나: 'idle' | 'loading' | 'success' | 'error'
async function load() {
setState({ status: "loading", data: null, error: null });
try {
const data = await api.get();
setState({ status: "success", data, error: null });
} catch (e) {
setState({ status: "error", data: null, error: e.message });
}
}
단일 status 필드는 state를 상호 배타적으로 만듭니다 — 로딩이면서 동시에 에러일 수 없습니다. 렌더링은 깔끔한 switch가 됩니다:
switch (state.status) {
case "loading": return <Spinner />;
case "error": return <Error message={state.error} />;
case "success": return <List data={state.data} />;
default: return <Idle />;
}
idle — 아직 시작 안 함(loading과 구별)
empty — 성공했지만 결과 없음(빈 화면 대신 "항목 없음" 표시)
refetching— 이전 데이터가 있으면서 새 데이터를 로딩 중(데이터 + 은은한 스피너 표시)
// React Query / SWR가 이 모든 것을 대신 모델링해 줌
const { data, isLoading, isError, error } = useQuery({ queryKey: ["x"], queryFn });
server-state 라이브러리는 이러한 state(그리고 캐싱, refetch, stale-while-revalidate)를 기본 제공합니다 — 보통 직접 만드는 것보다 낫습니다.
비동기 state를 느슨한 boolean이 아니라 단일 status로 제대로 모델링하면, UI 버그 전체(모순된 state, 누락된 로딩/에러/빈 상태 피드백)를 제거하고 렌더링 로직을 깔끔하고 완전하게 만듭니다.
state의 전체 집합(idle, loading, success, error, empty, refetching)을 인식하면 더 나은 UX로 이어지며, server-state 라이브러리가 이 모든 것을 처리한다는 것을 아는 것이 데이터 가져오기 코드를 단순하고 견고하게 유지하는 비결입니다.