useFetch는 서버에서 fetch한 데이터를 상태값으로 추적하고, 로딩과 에러 상태까지 함께 관리하는 훅입니다. 같은 fetch 로직을 컴포넌트마다 반복해서 작성하는 대신 하나의 훅으로 묶어 재사용할 수 있습니다.
먼저 useState만으로 만든 버전입니다. 응답 데이터, 로딩 여부, 에러를 각각 별도의 상태값으로 관리합니다. 컴포넌트가 언마운트됐을 때 응답 처리를 건너뛰기 위해 shouldCancel 플래그를 사용합니다.
javascript
React 18의 동시성 렌더링, 자동 배칭, useTransition, useId 등 핵심 기능으로 UI 응답성과 사용자 경험을 개선하는 방법을 정리합니다.
2024. 11. 24.FrontenduseRef와 커스텀 훅으로 상태 이력을 관리해 Undo/Redo 기능을 구현하는 방법을 정리합니다.
2022. 8. 29.FrontenduseToggle, useTimeout, useDebounce, useUpdateEffect 등 대부분의 프로젝트에서 재사용하기 좋은 React 커스텀 Hook을 코드와 함께 정리합니다.
2022. 7. 27.function useFetch(url) {
// 응답 JSON값
const [responseJSON,setResponseJSON] = useState(null);
// 로딩 상태값
const [isLoading,setIsLoading] = useState(true);
// 에러 상태값
const [error,setError] = useState(null);
useEffect(()=>{
// unMount시 cancel하기 위한 변수
let shouldCancel = false;
const callFetch = async () => {
setIsLoading(true);
try {
// 응답 성공 시
const response = await fetch(url);
const newResponseJSON = await response.json();
if(shouldCancel) return;
setResponseJSON(newResponseJSON);
setError(null);
} catch (newError) {
// 응답 실패 시
if(shouldCancel) return;
setError(newError);
setResponseJSON(null);
}
// 로딩은 응답 실패하나 성공하나 false로 변경
setIsLoading(false)
}
callFetch();
return () => {
shouldCancel = true
}
},[url])
return {
responseJSON,
isLoading,
error
}
}동작에는 문제가 없지만, 관리해야 할 상태값이 세 개로 나뉘어 있어 코드가 다소 복잡하게 느껴집니다. 응답 성공과 실패에 따라 여러 상태를 각각 맞춰 줘야 하는 점도 부담입니다.
흩어진 상태값을 하나로 모으기 위해 useReducer를 사용합니다. 로딩, 성공, 에러라는 상태 전이를 reducer 안에서 한 번에 정의하면, 컴포넌트에서는 dispatch로 의도만 표현하면 됩니다.
javascriptfunction reducer(state,{type,responseJSON,error}) { switch(type) { case 'loading': return {...state,isLoading:true}; case 'success': return {responseJSON,isLoading:false,error:null}; case 'error': return {responseJSON:null,isLoading:false,error}; default: throw new Error('Unknown action type'); } } function useFetch(url) { const [state,dispatch] = useReducer(reducer,{ responseJSON:null, isLoading:true, error:null }) useEffect(()=>{ let shouldCancel = false; const callFetch = async () => { dispatch({type:'loading'}) try { const response = await fetch(url); const newResponseJSON = await response.json(); if(shouldCancel) return; dispatch({type:'success',responseJSON:newResponseJSON}); } catch (newError) { if(shouldCancel) return; dispatch({type:'error',error:newError}); } } callFetch(); return () => { shouldCancel = true } },[url]) return state }
상태값을 하나하나 set하는 대신 loading, success, error 액션만 dispatch하면 되므로, 각 상황에서 어떤 상태가 되는지 reducer만 보면 한눈에 파악됩니다.
기본 버전과 reducer 버전 두 가지로 useFetch 훅을 만들어 봤습니다. 결과는 동일하지만, 기본 버전은 관리할 상태값이 많아 조금 복잡한 느낌이 듭니다. useReducer를 사용하면 상태 전이를 한곳에 모아 더 간결하고 직관적인 코드를 만들 수 있습니다.