대부분의 앱에서는 특정 컴포넌트가 로딩되자마자 데이터를 가져오기도 한다. 가령 사용자가 페이지를 방문하자마자 데이터를 가져오기도 한다.
바로 useEffect()
를 사용하면 된다. HTTP 요청 전송은 일종의 사이드 이펙트로, 컴포넌트의 상태를 바꾸기 때문이다.
메인 컴포넌트 함수 내에서 호출하지만 않는다면, 함수를 사이드 이펙트로 넣어도 된다.
그런데 만약, 메인 컴포넌트의 함수 일부분으로 호출되는 함수를 사이드 이펙트로 넣어버리면 어떻게될까?
함수가 호출되는 순간, 상태 업데이트가 발생하고, 컴포넌트 함수가 재평가 및 재렌더링되면서 함수가 다시 호출되는 무한루프가 발생하여 문제가 발생한다.
이를 피하기 위해 useEffect()
를 사용한다. useEffect()
는 컴포넌트가 렌더링되는 주기 안에서 사용되어야 하는 코드가 있을 때 유용하다. 컴포넌트가 재렌더링 될 경우는 좀 다르지만 말이다.
useEffect(() => {
fetchMoviesHandler();
}, []);
사이드 이펙트로 fetchMoviesHandler함수를 추가하고 두번째 인자로 디펜던시 배열를 작성하지 않으면, 패치 버튼을 클릭해도 fetchMoviesHandler 함수가 호출되지만, 동시에 컴포넌트 재평가가 발생할 때도 fetchMoviesHandler 함수가 호출된다.
그걸 막으려면 두 번째 인자로 빈 디펜던시 배열을 주면 된다.
그런데 모든 의존성은 의존성 배열에 표시해 두는 것이 좋다고 배웠는데, fetchMoviesHandler를 의존성으로 의존성 배열에 넣어야 하지 않을까?
그런데 그렇게 하면 fetchMoviesHandler가 함수이고 객체이기 때문에 컴포넌트가 재렌더링 될 때 마다 함수 역시 재생성되버린다..^^!
그러면 계~~속 재생성되니까 무한 루프가 발생해 버린다.
처음 방법 처럼 의존성 배열에 의존성으로 추가하지 않는 것이 한 방법이지만, 함수가 외부 상태를 사용한다면 의도치 않은 🐜버그가 발생할 수도 있다.
따라서 가장 좋은 해결책은 useCallback
훅을 사용하여 fetchMoviesHandler 함수를 감싸는 것이다.
// useCallback으로 fetchMoviesHandler 함수 감싸기
// async 예약어는 함수 앞에 넣어 주면 된다.
const fetchMoviesHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch("https://swapi.dev/api/films/");
if (!response.ok) {
throw new Error("Something went wrong!");
}
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
//의존성배열에 넣을 것이 없으므로 비워두면 된다.
}, []);
//HTTP 요청 사이드 이펙트
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
//의존성 배열에 모든 의존성을 넣어줘야 하므로 fetchMoviesHandler 함수도 넣는다.
이렇게 하면 무한루프에는 빠지지 않으면서도, 처음 화면이 렌더링될 때 데이터가 자동으로 패치되고, 버튼을 클릭할 때도 데이터를 패치할 수 있다.