axios
의cancellation
과lodash-debounce
에 대해서 알아보자 🤓
input
을 통해서 사용자로부터 query
를 받고, 그 query
를 이용해서 api와 통신하는 경우, 우리는 일반적으로 input
에 onChange
를 걸고 서버에 api call
을 보내게 된다. 하지만 이 경우, 사용자의 모든 입력에 따라서 api call
을 보내기 때문에 불필요하게 많은 request
를 보내게 된다.
axios
가 제공하는 canceltoken
을 사용하거나, lodash-debounce
라이브러리를 사용해서 아주 쉽게 해결할 수 있다!
Axios의
cancelTocken
을 사용해서request
를 캔슬할 수 있다.
(레퍼런스는 여기 참고)
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken((c) => cancel = c })
});
// cancel the request
cancel();
cancellationTocken
을 생성하기 위해서는 아래 두가지 방법중 선택하면 된다.
1. CancelToken.source
을 사용해서 생성
2. executor function
을 CancelToken constructor
에 passing해서 생성
useEffect(() => {
const request = Axios.CancelToken.source() // (*)
const fetchPost = async () => {
try {
const response = await Axios.get(`endpointURL`, {
cancelToken: request.token, // (*)
})
setPost(response.data)
setIsLoading(false)
} catch (err) {
console.log('There was a problem or request was cancelled.')
}
}
fetchPost()
return () => request.cancel() // (*)
}, [])
아래의 코드는 두번째 방법을 사용했다.
function useFetch(query: string, pageNum: number) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(false);
const [list, setList] = useState<MovieList>([]);
const [hasMore, setHasMore] = useState(false);
const sendQuery = useCallback(
async (query: string): Promise<any> => {
const CancelToken = axios.CancelToken; // (*)
let cancel: () => void; // (*)
if (query === '') return;
try {
await setIsLoading(true);
let res = await axios.get(
`${URL}?s=${query}&page=${pageNum}&apikey=${API_KEY}`,
{
cancelToken: new CancelToken(c => (cancel = c)), // (*)
}
);
const data: MovieList = await res?.data?.Search;
if (data) {
await setList(
(prev: MovieList): MovieList => {
return [...new Set([...prev, ...data])];
}
);
}
await setHasMore(data?.length > 0);
setIsLoading(false);
} catch (error) {
if (axios.isCancel(error)) return;
setError(error);
}
return () => cancel(); // (*)
},
[pageNum]
);
useEffect(() => {
setList([]);
}, [query]);
useEffect(() => {
sendQuery(query);
}, [query, pageNum, sendQuery]);
return { isLoading, error, list, hasMore };
}
export default useFetch;
debounce()
는 이벤트에 의해 특정 함수가 여러번 반복적으로 실행될 수 경우, 정해진 지연시간동안 반복된 호출을 1번만 호출하도록 제어한다.
_.debounce(callback, delay)
Lodash-debounce는 setTimeout
을 기반으로 작동한다. 따라서 컴포넌트가 렌더링 될때마다 새로운 setTimeout callback이 생성된다. 따라서 debounced callback을 레퍼런스로 저장해야 하는데, 우리는 useCallback
혹은 useRef
을 사용할 수 있다.
따라서 리액트에서 Lodash-debounce를 사용하기 위해서, 아래 두가지중 한가지를 선택하면 된다.
1. useCallback
을 사용
2. useRef
을 사용
아래의 코드는 첫번째 방법을 사용했다.
import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
import { MovieList } from 'types/types';
import { debounce } from 'lodash';
const API_KEY: string | undefined = process.env.REACT_APP_API_KEY;
const URL: string = `http://www.omdbapi.com/`;
function useFetch(query: string, pageNum: number) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(false);
const [list, setList] = useState<MovieList>([]);
const [hasMore, setHasMore] = useState(false);
const delayedQuery = useCallback( // (*)
debounce((q: string) => sendQuery(q), 500),
[]
);
const sendQuery = useCallback(
async (query: string): Promise<any> => {
if (query === '') return;
try {
await setIsLoading(true);
let res = await axios.get(
`${URL}?s=${query}&page=${pageNum}&apikey=${API_KEY}`
);
const data: MovieList = await res?.data?.Search;
if (data) {
await setList(
(prev: MovieList): MovieList => {
return [...new Set([...prev, ...data])];
}
);
}
await setHasMore(data?.length > 0);
setIsLoading(false);
} catch (error) {
setError(error);
}
},
[pageNum]
);
useEffect(() => {
setList([]);
}, [query]);
useEffect(() => {
delayedQuery(query); // (*)
}, [query, pageNum, delayedQuery]);
return { isLoading, error, list, hasMore };
}
export default useFetch;