로딩과 에러는 개별 메시지가 아니라 요청 상태와 데이터 유무에 따라 선택되는 렌더 분기표로 관리하면 더 안정적입니다.
요청 UI의 주 상태와 background refresh 축 — 상태 추적
첫 요청의 배타 status와 기존 data/empty를 유지하는 isRefreshing 축은 다르다. 같은 key 갱신 성공/실패 및 key 변경의 값과 화면 보존을 추적할 가치가 있다.
idle, loading, error, success의 data·empty 분기와 retry 전이 위에 기존 데이터를 유지하는 background isRefreshing 축을 분리한 요청 UI 상태 모델
Primary status × background refresh
첫 요청 상태와 백그라운드 갱신을 분리하기
첫 요청의 화면 상태와 기존 데이터를 갱신하는 진행 상태는 같은 축이 아닙니다. 주 상태는 한 번에 하나를 선택하고, isRefreshing은 data 또는 empty 화면을 유지한 채 뒤에서 새 요청이 진행 중임을 덧붙입니다.
첫 요청: 실패하면 결과 화면을 차단한다
최신 요청의 첫 reject는 차단 error이며 retry는 loading으로 돌아갑니다. empty는 실패가 아니라 성공 결과입니다.
같은 key 갱신: 기존 결과를 유지한다
같은 key 갱신 실패는 기존 data/empty를 유지하며 비차단 오류와 retry를 표시합니다. 새 resource key는 위 첫 요청의 loading으로 돌아가므로 이전 엔터티를 유지하지 않습니다.
원래 설명과 상세 기준
주 상태 · 배타적
idle → loading → success | error
idle: URL이나 시작 조건이 아직 없음
loading: 표시할 기존 데이터가 없는 첫 요청
success:data: 성공했고 표시할 항목이 있음
success:empty: 성공했지만 결과가 비어 있음
error: 첫 요청이 실패해 복구 행동이 필요함
직교 축 · 비차단
isRefreshing은 기존 UI를 보존
data 또는 empty 상태에서 같은 resource key를 refetch하면 기존 콘텐츠를 지우지 않고 진행 표시만 추가합니다. key에는 인증 주체·locale·query처럼 응답 정체성을 바꾸는 입력을 모두 포함합니다. 새 결과가 오면 교체하고, 갱신 실패는 기존 결과와 함께 비차단 오류·재시도 동선으로 보여줍니다.
AbortController는 불필요한 요청을 중단하고, active/latest request ID gate는 늦게 끝난 이전 요청이 현재 상태를 쓰지 못하게 합니다. finally도 같은 gate를 통과해야 합니다.
요청 이벤트가 주 상태와 화면 계약을 바꾸는 규칙
현재 상태
이벤트
다음 상태
사용자에게 보이는 화면
idle
유효한 URL로 시작
loading
스켈레톤·진행 안내, 중복 실행 제한
loading
항목과 함께 resolve
success:data
데이터와 다음 행동
loading
빈 결과로 resolve
success:empty
오류가 아닌 빈 상태 안내
loading
reject
error
원인 요약과 retry 행동
error
retry
loading
이전 오류를 정리하고 다시 요청
success
같은 key를 background refetch
success + 갱신 중
기존 data·empty UI를 그대로 유지
success + 갱신 중
최신 요청 resolve
success
새 결과로 교체하고 갱신 표시 종료
success + 갱신 중
최신 요청 reject
success + 갱신 오류
기존 결과를 유지하고 비차단 retry 제공
idle → loading
유효한 요청을 시작한다
표시할 결과가 없으므로 스켈레톤과 진행 안내를 보여주고 중복 실행을 제한합니다.
loading → success
resolve 결과는 data 또는 empty
항목이 있으면 데이터와 다음 행동을, 값이 비었으면 오류가 아닌 빈 상태 안내를 보여줍니다.
loading → error
reject 원인과 복구 행동을 제시한다
원인을 요약하고 사용자가 다시 요청할 수 있는 retry 동선을 제공합니다.
error → loading
retry는 새 요청 이벤트다
이전 오류를 정리하고 새 요청을 시작하며, 별도의 영구 화면 상태로 취급하지 않습니다.
success + refreshing
같은 key의 결과를 뒤에서 갱신한다
기존 data·empty 화면은 유지하고 진행 표시만 더합니다. 응답 정체성 key가 바뀌면 loading으로 돌아갑니다.
refresh completion
최신 완료만 현재 화면에 반영한다
resolve면 새 결과로 교체합니다. reject면 기존 결과를 유지하고 비차단 오류와 retry를 보여줍니다.
retry는 별도 화면 상태가 아니라 error에서 새 loading 요청을 시작하는 이벤트입니다. 응답 정체성 key가 바뀌면 이전 엔터티를 유지하지 않고 loading으로 돌아가며, empty는 요청 실패가 아니라 값이 비어 있는 성공 결과입니다.
이 예의 조건
원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분
내용
구분원도표 설명
내용첫 요청의 화면 상태와 기존 데이터를 갱신하는 진행 상태는 같은 축이 아닙니다. 주 상태는 한 번에 하나를 선택하고, isRefreshing은 data 또는 empty 화면을 유지한 채 뒤에서 새 요청이 진행 중임을 덧붙입니다.
구분원도표 조건·주석
내용idle: URL이나 시작 조건이 아직 없음
구분원도표 조건·주석
내용loading: 표시할 기존 데이터가 없는 첫 요청
구분원도표 조건·주석
내용success:data: 성공했고 표시할 항목이 있음
구분원도표 조건·주석
내용success:empty: 성공했지만 결과가 비어 있음
구분원도표 조건·주석
내용error: 첫 요청이 실패해 복구 행동이 필요함
구분원도표 설명
내용isRefreshing은 기존 UI를 보존
구분원도표 설명
내용data 또는 empty 상태에서 같은 resource key를 refetch하면 기존 콘텐츠를 지우지 않고 진행 표시만 추가합니다. key에는 인증 주체·locale·query처럼 응답 정체성을 바꾸는 입력을 모두 포함합니다. 새 결과가 오면 교체하고, 갱신 실패는 기존 결과와 함께 비차단 오류·재시도 동선으로 보여 줍니다.
구분원도표 설명
내용AbortController는 불필요한 요청을 중단하고, active/latest request ID gate는 늦게 끝난 이전 요청이 현재 상태를 쓰지 못하게 합니다. finally도 같은 gate를 통과해야 합니다.
구분원도표 표 행
내용현재 상태 | 이벤트 | 다음 상태 | 사용자에게 보이는 화면
구분원도표 표 행
내용idle | 유효한 URL로 시작 | loading | 스켈레톤·진행 안내, 중복 실행 제한
구분원도표 표 행
내용loading | 항목과 함께 resolve | success:data | 데이터와 다음 행동
구분원도표 표 행
내용loading | 빈 결과로 resolve | success:empty | 오류가 아닌 빈 상태 안내
구분원도표 표 행
내용loading | reject | error | 원인 요약과 retry 행동
구분원도표 표 행
내용error | retry | loading | 이전 오류를 정리하고 다시 요청
구분원도표 표 행
내용success | 같은 key를 background refetch | success + 갱신 중 | 기존 data·empty UI를 그대로 유지
구분원도표 표 행
내용success + 갱신 중 | 최신 요청 resolve | success | 새 결과로 교체하고 갱신 표시 종료
구분원도표 표 행
내용success + 갱신 중 | 최신 요청 reject | success + 갱신 오류 | 기존 결과를 유지하고 비차단 retry 제공
구분원도표 설명
내용표시할 결과가 없으므로 스켈레톤과 진행 안내를 보여 주고 중복 실행을 제한합니다.
구분원도표 설명
내용항목이 있으면 데이터와 다음 행동을, 값이 비었으면 오류가 아닌 빈 상태 안내를 보여 줍니다.
구분원도표 설명
내용resolve면 새 결과로 교체합니다. reject면 기존 결과를 유지하고 비차단 오류와 retry를 보여 줍니다.
구분원도표 설명
내용retry는 별도 화면 상태가 아니라 error에서 새 loading 요청을 시작하는 이벤트입니다. 응답 정체성 key가 바뀌면 이전 엔터티를 유지하지 않고 loading으로 돌아가며, empty는 요청 실패가 아니라 값이 비어 있는 성공 결과입니다.
전체 상태와 변화 근거
장면
현재 관찰값
설명
변화 이유
첫 결과 없음
현재 관찰값status: 배타적인 주 상태 / 기존 data: 없음
설명첫 요청은 loading 뒤 data·empty·error로 끝난다
변화 이유표시할 자료가 없는 대기를 분리한다
같은 key 갱신
현재 관찰값기존 결과: 유지 / refreshing: true
설명success 화면을 유지하며 isRefreshing을 켠다
변화 이유주 상태 success와 진행 축은 함께 있을 수 있다
갱신 완료
현재 관찰값성공: 새 data/empty / 실패: 기존 결과+retry
설명성공은 새 결과, 실패는 기존 결과와 비차단 오류로 남는다
변화 이유다른 resource key는 이전 엔터티를 유지하는 같은 갱신이 아니다
요청 UI의 주 상태와 background refresh 축 — 상태 추적
첫 요청의 배타 status와 기존 data/empty를 유지하는 isRefreshing 축은 다르다. 같은 key 갱신 성공/실패 및 key 변경의 값과 화면 보존을 추적할 가치가 있다.
idle, loading, error, success의 data·empty 분기와 retry 전이 위에 기존 데이터를 유지하는 background isRefreshing 축을 분리한 요청 UI 상태 모델
Primary status × background refresh
첫 요청 상태와 백그라운드 갱신을 분리하기
첫 요청의 화면 상태와 기존 데이터를 갱신하는 진행 상태는 같은 축이 아닙니다. 주 상태는 한 번에 하나를 선택하고, isRefreshing은 data 또는 empty 화면을 유지한 채 뒤에서 새 요청이 진행 중임을 덧붙입니다.
첫 요청: 실패하면 결과 화면을 차단한다
최신 요청의 첫 reject는 차단 error이며 retry는 loading으로 돌아갑니다. empty는 실패가 아니라 성공 결과입니다.
같은 key 갱신: 기존 결과를 유지한다
같은 key 갱신 실패는 기존 data/empty를 유지하며 비차단 오류와 retry를 표시합니다. 새 resource key는 위 첫 요청의 loading으로 돌아가므로 이전 엔터티를 유지하지 않습니다.
원래 설명과 상세 기준
주 상태 · 배타적
idle → loading → success | error
idle: URL이나 시작 조건이 아직 없음
loading: 표시할 기존 데이터가 없는 첫 요청
success:data: 성공했고 표시할 항목이 있음
success:empty: 성공했지만 결과가 비어 있음
error: 첫 요청이 실패해 복구 행동이 필요함
직교 축 · 비차단
isRefreshing은 기존 UI를 보존
data 또는 empty 상태에서 같은 resource key를 refetch하면 기존 콘텐츠를 지우지 않고 진행 표시만 추가합니다. key에는 인증 주체·locale·query처럼 응답 정체성을 바꾸는 입력을 모두 포함합니다. 새 결과가 오면 교체하고, 갱신 실패는 기존 결과와 함께 비차단 오류·재시도 동선으로 보여줍니다.
AbortController는 불필요한 요청을 중단하고, active/latest request ID gate는 늦게 끝난 이전 요청이 현재 상태를 쓰지 못하게 합니다. finally도 같은 gate를 통과해야 합니다.
요청 이벤트가 주 상태와 화면 계약을 바꾸는 규칙
현재 상태
이벤트
다음 상태
사용자에게 보이는 화면
idle
유효한 URL로 시작
loading
스켈레톤·진행 안내, 중복 실행 제한
loading
항목과 함께 resolve
success:data
데이터와 다음 행동
loading
빈 결과로 resolve
success:empty
오류가 아닌 빈 상태 안내
loading
reject
error
원인 요약과 retry 행동
error
retry
loading
이전 오류를 정리하고 다시 요청
success
같은 key를 background refetch
success + 갱신 중
기존 data·empty UI를 그대로 유지
success + 갱신 중
최신 요청 resolve
success
새 결과로 교체하고 갱신 표시 종료
success + 갱신 중
최신 요청 reject
success + 갱신 오류
기존 결과를 유지하고 비차단 retry 제공
idle → loading
유효한 요청을 시작한다
표시할 결과가 없으므로 스켈레톤과 진행 안내를 보여주고 중복 실행을 제한합니다.
loading → success
resolve 결과는 data 또는 empty
항목이 있으면 데이터와 다음 행동을, 값이 비었으면 오류가 아닌 빈 상태 안내를 보여줍니다.
loading → error
reject 원인과 복구 행동을 제시한다
원인을 요약하고 사용자가 다시 요청할 수 있는 retry 동선을 제공합니다.
error → loading
retry는 새 요청 이벤트다
이전 오류를 정리하고 새 요청을 시작하며, 별도의 영구 화면 상태로 취급하지 않습니다.
success + refreshing
같은 key의 결과를 뒤에서 갱신한다
기존 data·empty 화면은 유지하고 진행 표시만 더합니다. 응답 정체성 key가 바뀌면 loading으로 돌아갑니다.
refresh completion
최신 완료만 현재 화면에 반영한다
resolve면 새 결과로 교체합니다. reject면 기존 결과를 유지하고 비차단 오류와 retry를 보여줍니다.
retry는 별도 화면 상태가 아니라 error에서 새 loading 요청을 시작하는 이벤트입니다. 응답 정체성 key가 바뀌면 이전 엔터티를 유지하지 않고 loading으로 돌아가며, empty는 요청 실패가 아니라 값이 비어 있는 성공 결과입니다.
이 예의 조건
원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분
내용
구분원도표 설명
내용첫 요청의 화면 상태와 기존 데이터를 갱신하는 진행 상태는 같은 축이 아닙니다. 주 상태는 한 번에 하나를 선택하고, isRefreshing은 data 또는 empty 화면을 유지한 채 뒤에서 새 요청이 진행 중임을 덧붙입니다.
구분원도표 조건·주석
내용idle: URL이나 시작 조건이 아직 없음
구분원도표 조건·주석
내용loading: 표시할 기존 데이터가 없는 첫 요청
구분원도표 조건·주석
내용success:data: 성공했고 표시할 항목이 있음
구분원도표 조건·주석
내용success:empty: 성공했지만 결과가 비어 있음
구분원도표 조건·주석
내용error: 첫 요청이 실패해 복구 행동이 필요함
구분원도표 설명
내용isRefreshing은 기존 UI를 보존
구분원도표 설명
내용data 또는 empty 상태에서 같은 resource key를 refetch하면 기존 콘텐츠를 지우지 않고 진행 표시만 추가합니다. key에는 인증 주체·locale·query처럼 응답 정체성을 바꾸는 입력을 모두 포함합니다. 새 결과가 오면 교체하고, 갱신 실패는 기존 결과와 함께 비차단 오류·재시도 동선으로 보여 줍니다.
구분원도표 설명
내용AbortController는 불필요한 요청을 중단하고, active/latest request ID gate는 늦게 끝난 이전 요청이 현재 상태를 쓰지 못하게 합니다. finally도 같은 gate를 통과해야 합니다.
구분원도표 표 행
내용현재 상태 | 이벤트 | 다음 상태 | 사용자에게 보이는 화면
구분원도표 표 행
내용idle | 유효한 URL로 시작 | loading | 스켈레톤·진행 안내, 중복 실행 제한
구분원도표 표 행
내용loading | 항목과 함께 resolve | success:data | 데이터와 다음 행동
구분원도표 표 행
내용loading | 빈 결과로 resolve | success:empty | 오류가 아닌 빈 상태 안내
구분원도표 표 행
내용loading | reject | error | 원인 요약과 retry 행동
구분원도표 표 행
내용error | retry | loading | 이전 오류를 정리하고 다시 요청
구분원도표 표 행
내용success | 같은 key를 background refetch | success + 갱신 중 | 기존 data·empty UI를 그대로 유지
구분원도표 표 행
내용success + 갱신 중 | 최신 요청 resolve | success | 새 결과로 교체하고 갱신 표시 종료
구분원도표 표 행
내용success + 갱신 중 | 최신 요청 reject | success + 갱신 오류 | 기존 결과를 유지하고 비차단 retry 제공
구분원도표 설명
내용표시할 결과가 없으므로 스켈레톤과 진행 안내를 보여 주고 중복 실행을 제한합니다.
구분원도표 설명
내용항목이 있으면 데이터와 다음 행동을, 값이 비었으면 오류가 아닌 빈 상태 안내를 보여 줍니다.
구분원도표 설명
내용resolve면 새 결과로 교체합니다. reject면 기존 결과를 유지하고 비차단 오류와 retry를 보여 줍니다.
구분원도표 설명
내용retry는 별도 화면 상태가 아니라 error에서 새 loading 요청을 시작하는 이벤트입니다. 응답 정체성 key가 바뀌면 이전 엔터티를 유지하지 않고 loading으로 돌아가며, empty는 요청 실패가 아니라 값이 비어 있는 성공 결과입니다.
데이터 페칭 시 useEffect의 의존성 배열을 올바르게 사용하는 것은 매우 중요합니다.
빈 배열 ([]): Effect가 읽는 반응형 값이 없을 때 사용합니다. 프로덕션 마운트에서는 한 번 setup되지만, 개발 Strict Mode는 cleanup 검증을 위해 추가 setup → cleanup → setup을 실행하므로 “정확히 한 번” 계약으로 사용하면 안 됩니다.
변수 포함 ([id, category]): 특정 props나 state 값이 변경될 때마다 데이터를 다시 가져옵니다. 예를 들어, 사용자 ID나 검색 카테고리가 변경될 때 유용합니다.
주의사항
함수나 객체 참조: Effect가 읽는 함수나 객체가 렌더마다 새로 만들어지면 Effect도 다시 실행될 수 있습니다. 불필요한 의존성을 먼저 Effect 안으로 옮기고, 참조 안정성이 실제로 필요할 때 useCallback이나 useMemo를 사용합니다.
// 매 렌더마다 fetchData 참조가 바뀌므로 Effect도 다시 실행됨const fetchDataEveryRender = async () => { /* ... */ };useEffect(() => { fetchDataEveryRender();}, [fetchDataEveryRender]);// 일반적인 해결: 요청 함수를 Effect 안에 정의useEffect(() => { const fetchData = async () => { /* ... */ }; fetchData();}, []);// 외부에서 같은 함수 참조가 필요할 때만 안정화const stableFetchData = useCallback(async () => { /* ... */ }, []);useEffect(() => { stableFetchData();}, [stableFetchData]);
데이터 페칭 함수의 경우, 대부분 useEffect 내부에 정의하는 것이 일반적이고 간결합니다.
예시
DataFetcherWithLoading에서 요청 중에는 loading=true가 첫 분기를 선택합니다. 404 응답이면 response.ok 검사에서 throw하고, catch가 error를 저장한 뒤 현재 요청의 finally가 로딩을 끕니다. 다음 렌더는 오류 화면입니다. 200 응답의 json()이 null을 돌려주면 원 예제에는 null 분기가 없어 마지막의 data.title에서 렌더 오류가 납니다. 기본 예제는 { title, body } 형태의 게시물 응답을 전제로 합니다. 일반 API에 적용할 때는 저장 전에 모양을 확인하거나, 성공했지만 값이 없는 화면 분기를 더해야 합니다.
재시도 버튼의 window.location.reload()는 요청 하나만 다시 하는 동작이 아닙니다. 페이지와 컴포넌트 state를 다시 시작합니다. 입력을 보존하며 조회만 재시도하려면 뒤의 retry 이벤트처럼 요청 상태를 다시 시작합니다.
이 구현에서 AbortController는 중단 가능한 네트워크 작업을 취소하고, active와 최신 request ID는 늦게 끝난 이전 작업의 상태 쓰기를 차단합니다. 같은 requestKey의 성공 결과가 있으면 값이 null인 empty 결과도 구분해 보존하고 isRefreshing만 켭니다. key가 달라지면 이전 엔터티를 노출하지 않고 새 loading 상태를 사용합니다.
requestKey는 URL뿐 아니라 인증 주체, locale, query, 응답을 바꾸는 header나 body처럼 응답의 정체성을 바꾸는 모든 입력을 포함해야 합니다. 이 예제는 공개 게시글 조회라서 post:${postId}면 충분하지만 사용자별 응답이라면 사용자 식별자도 key에 넣습니다. cache, 화면 state, commit guard가 모두 같은 key를 써야 이전 사용자의 데이터가 잠깐 노출되는 일을 막을 수 있습니다.
options는 Effect의 반응형 의존성입니다. 호출부에서 인라인 객체를 렌더링할 때마다 만들면 참조가 계속 바뀌어 요청이 반복되므로, 바뀌어야 하는 원시 값으로 options를 구성하거나 실제로 필요할 때 useMemo로 참조를 안정화합니다. 의존성 배열에서 options를 임의로 빼면 오래된 옵션을 읽게 됩니다.
같은 key와 새 key의 화면 추적
useFetch의 cachedResult는 모든 게시글을 보관하는 캐시가 아니라 가장 최근 한 key의 성공 결과 보관함입니다. 같은 key의 재시도는 그 값을 유지하지만 다른 key로 바꾸면 이 예제는 다시 요청합니다. hasResolved는 성공한 값이 null인 경우도 기억하기 위해 필요합니다.
조건
주 상태와 데이터
추가 표시
post:1 첫 요청, 이전 결과 없음
loading, null
초기 로딩
같은 key의 성공 값 { title: 'A' } 뒤 retry
success, 기존 A
isRefreshing=true
그 갱신 요청이 실패
success, 기존 A
갱신 오류·retry, refreshing 종료
같은 key의 성공 값 null 뒤 retry
success, null
빈 결과 화면에서 갱신 중
key를 post:2로 변경
loading, null
이전 A를 보여 주지 않음
PostDetailWithHook의 useParams에는 /posts/:postId 라우트가 필요합니다. App.tsx (일부)의 Error Boundary 예제는 경계 배치만 발췌한 것으로, Router와 해당 Route의 element 안에 배치하는 나머지 구성이 필요합니다. 라우트가 없는 문맥에서 post:undefined와 /posts/undefined를 실제 게시글 요청으로 사용하는 것은 이 실습의 조건이 아닙니다.
혼자 풀기
현재 key가 post:2이고, 이전 post:1 setup의 cleanup은 끝났다고 가정합니다. 1번 응답이 늦게 성공하고, 현재 2번 요청은 HTTP 200·JSON null로 성공합니다. 화면에는 어떤 게시글과 안내가 나와야 하나요? 이어서 같은 post:2를 retry했는데 네트워크 실패가 나면 무엇을 남겨야 하나요? 이 가상의 null 응답을 실제 서버의 HTTP 404와 구별하세요.
값이 없는 성공과 요청 실패 구별
1번 응답은 canCommit()을 통과하지 못해 저장하지 않습니다. 2번 null은 성공 결과이므로 status='success', data=null이며 게시글 없음 안내를 고릅니다. 같은 key의 retry 중에는 이 안내와 갱신 중 표시를 유지합니다. 갱신 실패 뒤에도 success·null을 남기고 갱신 오류·retry를 함께 표시합니다. 최초 요청의 HTTP 404라면 response.ok에서 throw하여 error 경로로 갑니다. 이 코드가 404를 자동으로 success·null로 바꾸지는 않습니다.
JavaScript 프로젝트에서 경계 예제 사용하기
아래 원 TSX 예제는 TypeScript 문법을 이해하는 독자를 위한 별도 형식입니다. Vite의 React JavaScript 템플릿에서 TypeScript 설정을 추가하지 않았다면 다음 .jsx 버전을 선택합니다. Props, State, React.ErrorInfo는 타입 표기이지 실행할 JavaScript 값이 아닙니다.
예를 들어 기존 Router의 <Route path="/posts/:postId" ... />가 갖는 element를 <AppErrorBoundary><PostDetailWithHook /></AppErrorBoundary>로 둡니다. 경계를 추가한다는 이유로 이미 있는 Router를 다시 만들지 않습니다. 일반 요청 rejection은 요청의 catch가 처리하고, 이후 렌더에서 실제 throw가 발생했을 때 경계가 하위 화면을 대체합니다. useTransition이 반환한 startTransition 안의 throw는 현행 React 문서가 설명하는 별도 예외이며, 일반 타이머·Promise 콜백과 같은 범위로 일반화하지 않습니다.
내용useTransition이 반환한 startTransition 콜백 안에서 throw된 오류는 일반 비동기 콜백과 달리 Error Boundary로 전달될 수 있습니다.
구분원도표 설명
내용잘못된 입력, 손상된 캐시, 실패한 데이터 요청처럼 다시 throw하게 만든 조건을 고칩니다. hasError만 false로 바꾸면 같은 하위 트리가 즉시 다시 실패할 수 있습니다.
구분원도표 조건·주석
내용먼저 원인을 복구 잘못된 입력, 손상된 캐시, 실패한 데이터 요청처럼 다시 throw하게 만든 조건을 고칩니다. hasError만 false로 바꾸면 같은 하위 트리가 즉시 다시 실패할 수 있습니다.
구분원도표 설명
내용경계 상태를 reset하거나 key를 바꿔 경계·하위 트리를 새로 마운트합니다. remount는 내부 state도 초기화하므로 보존할 상태를 먼저 결정합니다.
구분원도표 조건·주석
내용복구 뒤 reset 또는 의도적인 remount 경계 상태를 reset하거나 key를 바꿔 경계·하위 트리를 새로 마운트합니다. remount는 내부 state도 초기화하므로 보존할 상태를 먼저 결정합니다.
전체 상태와 변화 근거
장면
현재 관찰값
설명
변화 이유
하위 render 실패
현재 관찰값경계: hasError로 전이 / 하위: 실패
설명경계 안의 하위 컴포넌트가 render 중 throw한다
변화 이유이벤트와 일반 timer의 오류는 별도 범위다
fallback 격리
현재 관찰값UI: fallback / 원인: 입력/캐시 등 교정
설명실패한 하위 트리만 fallback으로 표시하고 원인을 복구한다
변화 이유상태 reset만으로 반복 throw의 조건을 고치지는 않는다
reset 또는 remount
현재 관찰값reset: 경계 상태 복원 / remount: 내부 state 초기화
설명원인 복구 뒤 명시한 정책으로 하위 UI를 다시 시작한다
변화 이유보존할 state와 상위 경계의 오류 범위를 결정한다
데이터 페칭 에러는 try...catch로 처리할 수 있지만, 렌더링 중 예외가 발생하면 컴포넌트 트리 전체가 깨질 수 있습니다.
이때 Error Boundary를 두면 전체 페이지 다운 대신 문제 구역만 폴백 UI로 격리할 수 있습니다. 하위 트리의 렌더·생성자·생명주기 오류는 잡지만, 이벤트 핸들러, 일반 비동기 콜백, 서버 렌더링, Error Boundary 자체에서 발생한 오류는 잡지 않습니다.
import AppErrorBoundary from './components/AppErrorBoundary';import PostDetailWithHook from './components/PostDetailWithHook';export default function App() { return ( <AppErrorBoundary fallback={<p>게시글 화면을 불러오지 못했습니다.</p>}> <PostDetailWithHook /> </AppErrorBoundary> );}
배치 가이드
페이지 단위 경계: 라우트별 주요 화면을 감싸 전체 앱 장애를 방지합니다.
위젯 단위 경계: 외부 데이터 의존 위젯(차트, 에디터 등)을 개별 격리합니다.
재시도 정책: hasError만 초기화하면 같은 원인으로 즉시 다시 실패할 수 있습니다. 잘못된 입력·데이터·캐시를 먼저 복구하고, 하위 state까지 새로 시작해야 할 때는 복구 버전을 key로 사용해 subtree를 의도적으로 remount합니다. remount는 내부 state도 지우므로 보존 정책을 함께 정합니다.
로딩 상태와 에러 처리는 여기까지입니다.
이 장에서는 비동기 데이터 페칭 과정에서 발생하는 로딩 상태와 에러를 효과적으로 관리하고 사용자에게 피드백하는 일의 중요성에 대해 배웠습니다.
useState와 try...catch를 이용한 기본적인 구현 방법부터, useEffect의 의존성 배열 사용 시 주의사항, 그리고 커스텀 훅을 통한 로직 재사용까지 심화된 내용을 다루었습니다.
이제는 리액트 애플리케이션에서 견고하고 사용자 친화적인 데이터 페칭 로직을 구현할 수 있는 기초를 마련했습니다.
다음 절에서는 axios와 같은 인기 있는 HTTP 클라이언트 라이브러리를 사용하여 데이터 페칭을 더욱 편리하게 만드는 방법을 알아보겠습니다.
로딩과 에러 처리는 데이터 요청 주변의 부가 UI가 아니라, 사용자가 다음 행동을 판단할 수 있게 만드는 상태 설계입니다.