diff --git a/frontend/src/components/ui/StationSearchWindow/StationSearchBar.tsx b/frontend/src/components/ui/StationSearchWindow/StationSearchBar.tsx index 2c52dfcd3..168e56a02 100644 --- a/frontend/src/components/ui/StationSearchWindow/StationSearchBar.tsx +++ b/frontend/src/components/ui/StationSearchWindow/StationSearchBar.tsx @@ -1,7 +1,5 @@ import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; -import { useSearchStations } from '@hooks/tanstack-query/useSearchStations'; - import FlexBox from '@common/FlexBox'; import Loader from '@common/Loader'; @@ -17,15 +15,11 @@ const StationSearchBar = () => { handleCloseResult, showStationDetails, isFocused, - debouncedSearchWord, - } = useStationSearchWindow(); - - const { - data: searchResult, + searchResult, isLoading, isError, isFetching, - } = useSearchStations(debouncedSearchWord); + } = useStationSearchWindow(); return ( diff --git a/frontend/src/components/ui/StationSearchWindow/hooks/useStationSearchWindow.tsx b/frontend/src/components/ui/StationSearchWindow/hooks/useStationSearchWindow.tsx index 19a889663..79409ad97 100644 --- a/frontend/src/components/ui/StationSearchWindow/hooks/useStationSearchWindow.tsx +++ b/frontend/src/components/ui/StationSearchWindow/hooks/useStationSearchWindow.tsx @@ -11,7 +11,8 @@ import { googleMapActions } from '@stores/google-maps/googleMapStore'; import { markerInstanceStore } from '@stores/google-maps/markerInstanceStore'; import { useStationInfoWindow } from '@hooks/google-maps/useStationInfoWindow'; -import { fetchSearchedStations } from '@hooks/tanstack-query/useSearchStations'; +import type { SearchedStationResponse } from '@hooks/tanstack-query/useSearchStations'; +import { fetchSearchedStations, useSearchStations } from '@hooks/tanstack-query/useSearchStations'; import { useDebounce } from '@hooks/useDebounce'; import useMediaQueries from '@hooks/useMediaQueries'; @@ -33,6 +34,8 @@ export const useStationSearchWindow = () => { const [searchWord, setSearchWord] = useState(''); const [debouncedSearchWord, setDebouncedSearchWord] = useState(searchWord); + const [userSearchWord, setUserSearchWord] = useState(''); + const [isSubmitted, setIsSubmitted] = useState(false); const { openLastPanel } = useNavigationBar(); const { openStationInfoWindow } = useStationInfoWindow(); @@ -43,9 +46,17 @@ export const useStationSearchWindow = () => { setDebouncedSearchWord(searchWord); }, [searchWord], - 400 + 400, + isSubmitted ); + const { + data: searchResult, + isLoading, + isError, + isFetching, + } = useSearchStations(debouncedSearchWord); + const handleOpenResult = ( event?: MouseEvent | FocusEvent ) => { @@ -60,31 +71,6 @@ export const useStationSearchWindow = () => { setIsFocused(false); }; - const handleSubmitSearchWord = async (event: FormEvent) => { - event.preventDefault(); - - const searchWordFromForm = new FormData(event.currentTarget).get('station-search'); - - if (encodeURIComponent(String(searchWordFromForm)) === searchWord) { - handleOpenResult(); - - return; - } - - handleCloseResult(); - - const searchedStations = await fetchSearchedStations(searchWord); - const isSearchedStationExisting = - searchedStations !== undefined && searchedStations.stations.length > 0; - - if (isSearchedStationExisting) { - const [{ stationId, latitude, longitude }] = searchedStations.stations; - showStationDetails({ stationId, latitude, longitude }); - } - - queryClient.invalidateQueries({ queryKey: [QUERY_KEY_SEARCHED_STATION] }); - }; - const showStationDetails = async ({ stationId, latitude, longitude }: StationPosition) => { googleMapActions.moveTo({ lat: latitude, lng: longitude }); @@ -122,9 +108,47 @@ export const useStationSearchWindow = () => { } }; + const showStationDetailsIfFound = (searchedStations: SearchedStationResponse) => { + const isSearchedStationExisting = searchedStations?.stations.length > 0; + + if (isSearchedStationExisting) { + const [{ stationId, latitude, longitude }] = searchedStations.stations; + showStationDetails({ stationId, latitude, longitude }); + handleCloseResult(); + + queryClient.invalidateQueries({ queryKey: [QUERY_KEY_SEARCHED_STATION] }); + } + }; + + const handleSubmitSearchWord = async (event: FormEvent) => { + event.preventDefault(); + handleCloseResult(); + setIsSubmitted(true); + + const searchWordFromForm = new FormData(event.currentTarget).get('station-search'); + const encodedSearchWord = encodeURIComponent(String(searchWordFromForm)); + const isSameAsPreviousSearchWord = encodedSearchWord === userSearchWord; + + if (isSameAsPreviousSearchWord) { + return; + } + + setUserSearchWord(encodedSearchWord); + + if (searchWord === debouncedSearchWord) { + showStationDetailsIfFound(searchResult); + + return; + } + + const searchedStations = await fetchSearchedStations(encodedSearchWord); + showStationDetailsIfFound(searchedStations); + }; + const handleChangeSearchWord = ({ target: { value } }: ChangeEvent) => { const searchWord = encodeURIComponent(value); + setIsSubmitted(false); handleOpenResult(); setSearchWord(searchWord); }; @@ -137,5 +161,9 @@ export const useStationSearchWindow = () => { showStationDetails, isFocused, debouncedSearchWord, + searchResult, + isLoading, + isError, + isFetching, }; }; diff --git a/frontend/src/hooks/tanstack-query/useSearchStations.ts b/frontend/src/hooks/tanstack-query/useSearchStations.ts index 51ec9129f..371c1ab79 100644 --- a/frontend/src/hooks/tanstack-query/useSearchStations.ts +++ b/frontend/src/hooks/tanstack-query/useSearchStations.ts @@ -7,7 +7,7 @@ import { SEARCH_SCOPE } from '@constants/stationSearch'; import type { SearchedCity, SearchedStation } from '@type/stations'; -interface SearchedStationResponse { +export interface SearchedStationResponse { stations: SearchedStation[]; cities: SearchedCity[]; } diff --git a/frontend/src/hooks/useDebounce.ts b/frontend/src/hooks/useDebounce.ts index 990de3738..44cdaa57d 100644 --- a/frontend/src/hooks/useDebounce.ts +++ b/frontend/src/hooks/useDebounce.ts @@ -1,11 +1,20 @@ import { useEffect, useCallback } from 'react'; -export const useDebounce = (func: (param: T) => void, dependencies: string[], delay: number) => { +export const useDebounce = ( + func: (param: T) => void, + dependencies: string[], + delay: number, + shouldClear?: boolean +) => { const callback = useCallback(func, dependencies); useEffect(() => { const timeout = setTimeout(callback, delay); + if (shouldClear) { + clearTimeout(timeout); + } + return () => clearTimeout(timeout); - }, [callback, delay]); + }, [callback, delay, shouldClear]); };