React Hooks 도입 후 Custom Hook으로 API 호출 로직 정리하기
배경
프로젝트에 React Hooks를 본격적으로 도입하기 시작했다. 가장 먼저 개선하고 싶었던 부분은 API 호출 로직이었다. 기존에는 각 컴포넌트마다 componentDidMount에서 fetch를 호출하고, loading/error state를 관리하는 보일러플레이트 코드가 반복되고 있었다.
기존 방식의 문제점
class UserProfile extends React.Component {
state = { data: null, loading: true, error: null };
componentDidMount() {
fetch('/api/user')
.then(res => res.json())
.then(data => this.setState({ data, loading: false }))
.catch(error => this.setState({ error, loading: false }));
}
render() {
const { data, loading, error } = this.state;
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <div>{data.name}</div>;
}
}
이런 패턴이 10개 이상의 컴포넌트에 중복되어 있었다.
Custom Hook 구현
function useApi(url) {
const [state, setState] = useState({
data: null,
loading: true,
error: null
});
useEffect(() => {
let cancelled = false;
fetch(url)
.then(res => res.json())
.then(data => {
if (!cancelled) {
setState({ data, loading: false, error: null });
}
})
.catch(error => {
if (!cancelled) {
setState({ data: null, loading: false, error });
}
});
return () => { cancelled = true; };
}, [url]);
return state;
}
컴포넌트 unmount 시 state 업데이트를 방지하기 위한 cleanup 로직도 추가했다.
사용 예시
function UserProfile() {
const { data, loading, error } = useApi('/api/user');
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <div>{data.name}</div>;
}
코드가 확실히 간결해졌다. Class 컴포넌트 대비 30% 정도 코드량이 줄었고, 로직 재사용이 가능해졌다.
추가 개선 계획
현재는 GET 요청만 지원하는데, POST/PUT 같은 mutation 로직도 별도 Hook으로 분리할 예정이다. SWR이나 React Query 같은 라이브러리도 검토 중이지만, 프로젝트 규모상 당장은 이 정도면 충분해 보인다.