만약 받아온 알파벳순으로 정렬되어있는 리스트에서 필요한값을 찾는데 z
로 시작되는 값이 필요하다면 유저는 제일 아래까지 탐색해서 원하는 값을 찾아야 할것이다.
리스트에서 원하는 값을 빠르게 찾을 수 있도록 검색기능을 추가해 필터의 사용성을 높여보았다.
const { data: brands } = useBrands();
서버에서 브랜드리스트를 받아와서 캐싱해놓는 과정을 진행한다 (react-query사용)
데이터를 캐싱해놓는다면 검색할때마다 서버로 데이터 요청을 보내지않고 클라이언트에서 받아온 데이터에서 필터링해서 보여줄 수 있다.
캐싱된 데이터를 사용한다면 서버로 불필요한 데이터를 줄일 수 있어 유저에게 보여주는 시간도 빨라진다.
브랜드리스트에서 검색한 브랜드리스트를 리턴해주는 훅을 만들어 사용한다.
위 훅에 사용되는 로직은 두군데 컴포넌트에서 사용될 예정이여서 중복로직을 작성하기 싫어 훅으로 빼내었다.
const useBrandSearch = () => {
const { data: brands } = useBrands();
const [searchTerm, setSearchTerm] = useState('');
const [filteredBrands, setFilteredBrands] = useState<AllBrandsResponse[]>([]);
useEffect(() => {
if (brands) {
const filteredList = brands.filter((brand) => {
return brand.nameEn.toLowerCase().includes(searchTerm.toLowerCase());
});
setFilteredBrands(filteredList);
}
}, [searchTerm, brands]);
const renderSearchInput = () => {
return (
<InputWrapper>
<SearchInput
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</InputWrapper>
);
};
return {
searchTerm,
filteredBrands,
renderSearchInput,
};
};
export default useBrandSearch;
검색어, 필터된브랜드리스트, 검색어인풋창렌더링함수
const { filteredBrands, renderSearchInput, searchTerm } = useBrandSearch();
리스트에서 유저가 브랜드를 클릭했을 때 만약 필터를 취소하고싶을때 아래와 같이 상단에 선택된 브랜드를 표기해주지 않는다면 유저는 1)다시 검색하거나 2)본인이 선택한 브랜드를 찾으러 리스트를 내리거나
둘 중의 하나의 행동을 해야한다.
사용자 입장에서는 매우 번거로울 수 있는 일이기에 선택 된 브랜드를 상단에 표기해주고 삭제할 수 있도록 해주어 편리성을 증가시켜주었다.
검색어를 입력했을 때 검색어와 일치하는 부분을 하이라이팅 시켜준다.
검색어와 name을 인자로 전달해 매칭되는 문자열의 스타일을 변경해주는 훅을 사용한다.
const useMatchTerm = (searchTerm: string, text: string) => {
const escapedSearchTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escapedSearchTerm})`, 'gi');
const splitName = text.split(regex);
const renderHighlightMatchingText = () => {
return splitName.map((part, index) => {
const isMatch = part.toLowerCase() === searchTerm.toLowerCase();
return isMatch ? <b key={index}>{part}</b> : part;
});
};
return { renderHighlightMatchingText };
};
export default useMatchTerm;
escapedSearchTerm
검색어에서 특수문자들을 정규식 패턴으로 인식되지 않고 문자열로 인식될 수 있게 끔 이스케이프 처리를 해준 검색어로 만든다.
[.*+?^${}()|[\]\\]
*, ., +, ?, ^, $, {}, (), |, [], \\
문자들이 해당된다.regex
new RegExp()를 사용하여 escapedSearchTerm을 포함하는 패턴을 생성
첫번째로 전달 된 인자 : 이스케이프 처리 된 문자열
두번째 매개변수 : gi 정규식 플래그
여기서 소괄호로 정규표현식을 묶은건 일치하는 패턴을 하나의 그룹으로 묶어주는 역할을 하기 때문이다.
splitName
const text = 'this is test sentence';
const searchTerm = 'is';
const escapedSearchTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regexWithParentheses = new RegExp(`(${escapedSearchTerm})`, 'gi');
const regexWithoutParentheses = new RegExp(`${escapedSearchTerm}`, 'gi');
const splitNameWithParentheses = text.split(regexWithParentheses);
const splitNameWithoutParentheses = text.split(regexWithoutParentheses);
console.log('With Parentheses:', splitNameWithParentheses);
console.log('Without Parentheses:', splitNameWithoutParentheses);
// With Parentheses: [ 'th', 'is', ' is test sentence' ]
// Without Parentheses: [ 'th', 'is', ' ', ' is test sentence' ]
renderHighlightMatchingText
입력된 검색어기준으로 분리된 배열을 map으로 돌면서 매치된다면 <b>
태그로 스타일을 변경해서 렌더링해준다.
const { renderHighlightMatchingText } = useMatchTerm(
searchTerm,
brand.nameEn
);
<div>{renderHighlightMatchingText()}</div>
이 기능을 작업하면서 초반에는 훅으로 작성하지않고 기능이 구현되게끔 작업을 마친 뒤 공통으로 추출할 수 있는 부분을 확인하여 커스텀훅을 2개를 만들게 되었다.
훅으로 추출해내어 적용하게되니 어떤동작을하는지와 그에 관련된 상세구현은 따로 확인할 수 있어 수정도 편하고 가독성도 높아지니 훅을 더 잘써야겠따는 생각이 들었다 👀
금방 끝낼 기능이라고 생각했는데 하다보니 생각보다 신경 쓸 부분이 많았따