const getSearch = async () => {
try {
const response = await getAPI<SearchResult[]>(`/api/quiz/search-bar?keyword=${searchInput}`)
setRelativeSearch(response.data)
console.log("퀴즈 검색",response.data);
} catch (error) {
console.log('error', error);
}
}
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchInput(event.target.value);
getSearch();
};
return(
<div className="absolute top-full left-0 right-0 bg-white border border-t-0 z-10">
{searchInput &&
relativeSearch.map((result) => ( // searchResults 결과
<div key={result.id} className="p-2 hover:bg-gray-100" onClick={()=>navigate('')}>
{result.title}
</div>
))}
</div>
)
검색어를 입력할 때마다 get요청을 보내고 있다
검색어 입력 시 딜레이 적용하기
: 이 방법은 가장 간단한 방법으로, 입력이 발생할 때마다 일정 시간(예: 300ms) 딜레이 후에 API 요청을 보낸다. 이 방법은 구현이 간단하고 빠르게 동작하지만, 사용자 경험에 영향을 줄 수 있다. 사용자가 입력을 중단했을 때도 딜레이가 발생하기 때문이다. (비추천)
Debounce 함수 사용하기
: 디바운스 함수를 사용하면 사용자가 입력을 계속할 때는 API 요청을 보내지 않고, 입력이 일정 시간 동안 멈추었을 때만 요청을 보낸다. 이 방법은 사용자 경험을 개선할 수 있으며, 중복 요청을 방지한다. 구현이 조금 복잡할 수 있지만, 좋은 선택이다.
API 요청 취소 기능 추가
: 이 방법은 Axios와 같은 HTTP 라이브러리에서 제공하는 요청 취소 기능을 사용하여 중복 요청을 방지하고, 사용자가 새로운 입력을 시작할 때 이전 요청을 취소한다. 하지만 이 방법은 주로 다른 상황에서 HTTP 요청을 관리하고 취소하는 데 사용된다. (사용 목적 불일치)
HTTP 요청을 관리하고 취소해야 하는 주요 상황은 다음과 같다
1. 사용자 인터페이스 관련 상황 : 사용자가 페이지를 떠나거나 다른 작업을 수행할 때 이전 요청을 취소하고 새로운 요청을 보낼 때.
2. 연속적인 요청 관리 : 사용자가 반복적으로 동일한 요청을 보낼 때 이전 요청을 취소하고 최신 요청만 처리할 때.
3. 빠른 응답 필요 : 빠른 응답이 필요한 상황에서 이전 요청을 취소하고 새로운 요청을 보낼 때.
4. 네트워크 문제 처리 : 네트워크 연결이 불안정한 경우 요청을 취소하고 사용자에게 알리는 경우.
5. 자원 관리 : 리소스 효율성과 메모리 누수 방지를 위해 불필요한 HTTP 요청을 취소할 때.
Debounce 함수는 과도한 요청, 처리를 수행하게 될 경우 발생할 수 있는 성능 저하를 막기 위해 이를 효과적으로 제어하여 성능을 개선하는 방법 중 하나이다.
이 함수는 연속적으로 호출되는 함수 중 마지막 함수만 호출한다.
즉 입력이 멈춘 후에만 함수를 실행한다.
// useDebounce.ts
import { useEffect, useState } from 'react';
const useDebounce = (value: string, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
}; //value 변경 시점에 clearTimeout
}, [value]);
return debouncedValue;
};
export default useDebounce;
// SearchBar.tsx
import { useEffect, useState } from 'react';
import { getAPI } from '@/apis/axios';
import useDebounce from '@/hooks/useDebounce';
import { useNavigate } from 'react-router';
const SearchBar = () => {
type SearchResult = {
title: string;
id: number;
};
const [searchInput, setSearchInput] = useState('');
const [relativeSearch,setRelativeSearch] = useState<SearchResult[]>([]);
const debouncedSearchTerm = useDebounce(searchInput, 200);
const navigate = useNavigate();
useEffect(() => {
if(debouncedSearchTerm) {
getSearchResult();
} else {
setRelativeSearch([]);
}
},[debouncedSearchTerm]);
const getSearchResult = async () => {
try {
const response = await getAPI<SearchResult[]>(`/api/quiz/search-bar?keyword=${searchInput}`)
setRelativeSearch(response.data)
console.log("퀴즈 검색",response.data);
} catch (error) {
console.log('error', error);
}
}
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchInput(event.target.value);
};
return (
<div className="relative">
<form>
<div className="relative">
<input type="search" id="default-search"
className="block w-full m-0 h-[37px] p-4 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search" required
onChange={handleSearchChange}
/>
<button className="absolute inset-y-0 right-0 flex items-center pr-3 ">
<svg className="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
</svg>
</button>
</div>
</form>
<div className="absolute top-full left-0 right-0 bg-white border border-t-0 z-10">
{searchInput &&
relativeSearch.map((result) => ( // searchResults 결과
<div key={result.id} className="p-2 hover:bg-gray-100 cursor-pointer" onClick={()=>navigate('')}> {/*퀴즈 상세페이지로 이동*/}
{result.title}
</div>
))}
</div>
</div>
);
};
export default SearchBar;
위의 개선 사항을 적용하먼 검색어를 입력할 때 빠르게 API 요청을 보내고, 중복 요청을 방지하며, 딜레이를 적용하여 부드러운 사용자 경험을 제공할 수 있다!