지하철이나 엘리베이터에서 앱을 쓰다 보면 네트워크가 툭툭 끊긴다. 그런데 앱은 그걸 모르고 로딩 스피너만 계속 돌린다. 사용자 입장에선 앱이 멈춘 것처럼 보인다. 요청이 타임아웃 날 때까지 기다렸다가 뜬금없는 에러가 뜨는 것도 별로다.
연결 상태를 미리 알면 이걸 다 피할 수 있다. 끊겨 있으면 배너를 띄워 "지금 오프라인이에요"라고 알려주고, 아예 요청을 보내지 않으면 된다. React Native에서는 @react-native-community/netinfo가 이 역할을 한다.
설치
npx expo install @react-native-community/netinfo
현재 상태 한 번 읽기
일단 지금 연결돼 있는지 한 번 확인하는 건 fetch()로 된다. 헷갈리지만 네트워크 요청이 아니라 NetInfo의 상태 조회 메서드다.
import NetInfo from '@react-native-community/netinfo';
const state = await NetInfo.fetch();
console.log(state.isConnected); // 네트워크에 물려 있나
console.log(state.isInternetReachable); // 실제로 인터넷이 되나
여기서 isConnected와 isInternetReachable을 꼭 구분해야 한다. 와이파이에 붙어 있어도(=isConnected true) 로그인 페이지로 가로채는 카페 와이파이처럼 인터넷은 안 되는(=isInternetReachable false) 경우가 흔하다. 진짜 판단은 isInternetReachable로 해야 한다.
상태 변화 구독하기
한 번 읽는 것보다 중요한 건 변화를 계속 듣는 거다. addEventListener가 상태가 바뀔 때마다 콜백을 준다. 훅으로 감싸두면 어디서든 쓸 수 있다.
import { useEffect, useState } from 'react';
import NetInfo from '@react-native-community/netinfo';
export function useOnline() {
const [online, setOnline] = useState(true);
useEffect(() => {
// 구독 해제 함수를 그대로 반환
return NetInfo.addEventListener((state) => {
setOnline(state.isConnected === true && state.isInternetReachable !== false);
});
}, []);
return online;
}
isInternetReachable은 확인 중일 때 null이 올 수 있어서, !== false로 걸러 아직 모르는 동안은 온라인으로 취급했다. 이제 오프라인 배너는 이 한 줄이면 된다.
function OfflineBanner() {
const online = useOnline();
if (online) return null;
return <Text style={styles.banner}>인터넷 연결이 끊겼어요</Text>;
}
요청 보내기 전에 막기
배너만으로는 부족하다. 끊긴 상태에서 버튼을 누르면 애초에 요청을 안 보내는 게 낫다.
async function submit() {
const { isInternetReachable } = await NetInfo.fetch();
if (isInternetReachable === false) {
showToast('오프라인 상태예요. 연결 후 다시 시도해 주세요.');
return;
}
await api.post('/orders', payload);
}
정리하면 세 가지다. 상태는 isInternetReachable로 판단할 것, addEventListener로 변화를 구독해 배너를 띄울 것, 그리고 요청 직전에 한 번 더 확인해 헛된 호출을 막을 것. 스피너만 돌던 화면이 "지금 오프라인" 한 줄로 바뀌는 것만으로도 체감이 확 달라진다.