사용자가 플레이리스트를 쉽게 검색하고 탐색할 수 있도록 검색 기능을 구현했습니다.
검색 시에는 검색어와 관련된 콘텐츠를, 검색하지 않을 때는 최근 검색어 목록이 나타나도록 구성했습니다.
이 글에서는 검색어 기능 구현 과정에서 직면한 문제와 해결 방법을 공유합니다.
상태 관리에서 props
는 간단한 데이터 전달에는 유용하지만, 여러 컴포넌트를 거쳐 데이터를 전달해야 할 경우 유지보수가 어려워질 수 있습니다. 이를 방지하기 위해 Prop Drilling
문제를 해결하는 store
를 활용해 상태를 중앙에서 관리했습니다.
사용자가 검색한 검색어와 최근 검색어 구현을 위한 검색 목록을 하나의 store
에 관리하여 기능을 구현하였지만 검색어를 보내는 과정에서 persist
로 인해 해당 검색어를 찾아 api 함수
보내야 하는 문제가 있어 검색어와 검색어 목록을 따로 관리하였습니다.
검색어에 따라 특정 데이터가 반환되어야 하지만, 플레이리스트의 모든 데이터가 반환되는 문제가 있었습니다. 이를 해결하기 위해 데이터 조회 과정과 상태 관리 방식을 점검하며 다음과 같은 방법을 시도했습니다.
supabase에서 query function sql editor를 통해 직접 데이터 조회
useinfinitequery querykey 확인
network 탭을 통한 payload를 확인
작성한 query function
을 사용하여 직접 sql editor
를 통해 query를 조회하였지만 정상적인 데이터를 반환하는 것을 확인하였습니다.
이 경우에는 MainPlayList Page
에서 반환하는 데이터가 같아서 중복된 queryKey
혹은 api url
이 맞는 확인하였습니다.
이 단계에서 원인을 찾을 수 있었습니다. 검색어를 직접 입력하고 해당 검색어를 클릭 시 navigate
를 통해 search page
로 이동하면서 store
에 있던 검색어가 렌더링 되면서 초기화 되는 현상이 원인이였습니다.
그래서 navigate
에서 state
를 통해 특정을 경로로 이동 시 추가적인 데이터를 전달할 수 있도록 하여searchTerm
데이터를 해당 url state
로 보내 사용하였습니다.
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate();
navigate(`/search?search=${searchTerm}`, { state: searchTerm })
import { useLocation } from 'react-router-dom'
const location = useLocation()
const searchTerm = location.state
검색한 내용들을 저장하여 사용자가 검색한 내용들을 볼 수 있도록 구현하려고 했습니다.
우선 최근 검색어를 구현하기 위한 요구사항을 먼저 작성하였습니다.
검색어가 중복되어 저장되면 안됩니다.
store
혹은 state
를 통해 저장하면 렌더링 시 초기화 되는 현상으로 인해 다른 저장소가 필요합니다.
검색어 중복을 방지하고 렌더링 시 데이터를 유지하기 위해 new Set
으로 중복을 제거하고, localStorage
를 사용해 상태를 브라우저에 저장하도록 구현했습니다. 이를 통해 사용자가 페이지를 새로고침해도 최근 검색어가 유지되도록 했습니다.
interface SearchTermListProps {
searchTermList: { id: string; term: string }[] // id와 term을 포함한 객체 배열
setSearchTermList: (newState: { id: string; term: string }[]) => void
addSearchTerm: (newTerm: string) => void
}
export const useSearchTermListStore = create<SearchTermListProps>()(
persist(
(set, get) => ({
searchTermList: [],
setSearchTermList: newState => set({ searchTermList: newState }),
addSearchTerm: (newTerm: string) => {
if (!newTerm.trim()) return
const currentList = get().searchTermList
const newId = crypto.randomUUID()
const newEntry = { id: newId, term: newTerm }
// 중복 제거 및 최대 10개 유지
const updatedList = [
newEntry, // 최신 검색어를 맨 앞에 추가
...currentList.filter(item => item.term !== newTerm) // 중복 제거
]
const limitedList = updatedList.slice(0, 10) // 최대 10개 유지
set({ searchTermList: limitedList })
}
}),
{
name: 'searchTermList',
storage: createJSONStorage(() => localStorage)
}
)
)
React에서 key는 요소를 고유하게 식별해 가상 DOM에서 효율적인 업데이트를 돕는 역할을 합니다. 그러나 index는 요소의 순서에 의존하므로, 데이터가 변경될 때 예상치 못한 UI 업데이트 문제가 발생할 수 있습니다. 이를 해결하기 위해 id를 생성해 고유한 key로 사용했습니다.
의도치 않게 빈문자열이 저장되는 상황이 발생하여 이를 해결하기 위해 trim()
을 사용하여 해결하였습니다.
이번 검색 기능 구현을 통해 상태 관리의 중요성과 store 및 localStorage를 활용한 데이터 유지 방식에 대해 깊이 이해할 수 있었습니다. 또한, 브라우저의 network 탭과 debug 도구를 활용해 오류를 보다 능동적으로 해결할 수 있다는 점을 배웠습니다.