const useFetch = (endpoint, options = {}) => {
const { isMocked = false, method = "get", params = {}, body = {} } = options;
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = async () => {
console.log(
`endpoint: ${endpoint} method: ${method} params: ${JSON.stringify(params)} body: ${JSON.stringify(body)}`,
);
try {
setLoading(true);
const service = isMocked ? mockService : apiService;
let response;
switch (method) {
case "GET":
response = await service.get(endpoint, params);
break;
case "POST":
response = await service.post(endpoint, body);
break;
case "PUT":
response = await service.put(endpoint, body);
break;
case "DELETE":
response = await service.delete(endpoint, params);
break;
default:
response = await service.get(endpoint, params);
}
setData(response);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [endpoint, method, JSON.stringify(params), JSON.stringify(body)]);
return { data, loading, error, fetchData };
};
다음의 커스텀 훅을 만들고 사이트를 테스트를 했다.

그런데 /lectures/categories부분에서 2번 호출되는 경우가 있었다.
먼저 lectures/categories를 호출하는 컴포넌트가 어디인지부터 확인해야했다.
const Home = () => {
const [selectedCategory, setSelectedCategory] = useState(0);
const {
data: topLanguages,
loading: topLanguagesLoading,
error: topLanguagesError,
} = useFetch(ENDPOINTS.TOP_LANGUAGES, { isMocked: true, method: "GET" });
const {
data: lectures,
loading: lecturesLoading,
error: lecturesError,
fetchData: fetchLectures,
} = useFetch(ENDPOINTS.LECTURES, {
isMocked: false,
method: "get",
params: { category: categoryKeywords[selectedCategory].eng_category },
});
useEffect(() => {
fetchLectures();
}, [selectedCategory]);
if (topLanguagesLoading || lecturesLoading) return <Loading />;
if (topLanguagesError || lecturesError) return <Error />;
return <컴포넌트 />;
};
selectedCategory를 통해 카테고리를 선택하고, 그 값이 바뀌면 fetchLectures를 한다.
useFetch(ENDPOINTS.LECTURES)를 해야하는 경우는 다음과 같다.
Home 컴포넌트에 들어갈 때selectedCategory가 변경될 때위의 코드는 useFetch내부의 useEffect를 통해 최초에 호출된다.

최초 Home 컴포넌트에 진입 시, 1번째 useFetch가 작동한다.
그리고, selectedCategory도 Home 컴포넌트에 진입 시 useEffect에 의해 fetchLectures를 한 번 더 호출하는 것이다.
Home 컴포넌트에 들어간 후 selectedCategory를 바꾸면, 이때는 제대로 fetchLectures를 한 번만 호출한다.
즉, 최초 Home 컴포넌트에 진입 시 fetch를 2회 호출한다.
Home 컴포넌트에 진입한 상태에서 selectedCategory를 바꿀 땐 정상적으로 fetch를 1회 호출한다.
1번의 경우를 수정하면 되는 것이다.
useEffect에는 의존성배열을 제공하기에 이 값이 변경될때마다 호출하도록 할 수 있다.
즉, useFetch 커스텀 훅에 의존성배열을 입맛대로 조정할 수 있는 기능을 추가하면 된다.
const useFetch = (endpoint, options = {}) => {
const {
isMocked = false,
method = "get",
params = {},
body = {},
dependencies = {},
} = options;
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData();
}, [
isMocked,
endpoint,
method,
JSON.stringify(params),
JSON.stringify(body),
JSON.stringify(dependencies),
]);
const fetchData = async () => {
console.log(
`endpoint: ${endpoint} method: ${method} params: ${JSON.stringify(params)} body: ${JSON.stringify(body)} dependencies: ${JSON.stringify(dependencies)}`,
);
try {
setLoading(true);
const service = isMocked ? mockService : apiService;
let response;
switch (method) {
case "GET":
response = await service.get(endpoint, params);
break;
case "POST":
response = await service.post(endpoint, body);
break;
case "PUT":
response = await service.put(endpoint, body);
break;
case "DELETE":
response = await service.delete(endpoint, params);
break;
default:
response = await service.get(endpoint, params);
}
setData(response);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
return { data, loading, error, fetchData };
};
dependencies라는 값을 추가한다.
const Home = () => {
const [selectedCategory, setSelectedCategory] = useState(0);
const {
data: topLanguages,
loading: topLanguagesLoading,
error: topLanguagesError,
} = useFetch(ENDPOINTS.TOP_LANGUAGES, { isMocked: true, method: "GET" });
const {
data: lectures,
loading: lecturesLoading,
error: lecturesError,
} = useFetch(ENDPOINTS.LECTURES, {
isMocked: false,
method: "get",
params: { category: categoryKeywords[selectedCategory].eng_category },
dependencies: { selectedCategory: selectedCategory },
});
if (topLanguagesLoading || lecturesLoading) return <Loading />;
if (topLanguagesError || lecturesError) alert("에러가 발생했습니다.");
return <컴포넌트 />;
그리고 Home 컴포넌트에서는 useFetch를 사용할때 dependencies를 넘긴다.
이렇게 되면, dependencies가 변경되면 1회면 호출을 하게 된다.

최종적으로 최초로 페이지에 접근해도 fetch를 1번만 하게 된다.