S-FLEX(NOMFLIX CLONE) - Search

짜스의 하루 ·2024년 7월 4일

지이이이잉이이인짜 지이이이이잉이이ㅣㄴ짜
오래 걸렸고 세상 오류 다 만난 부분 ... 😂 인생 진짜 엄청 오류가 많이 났ㅇ었다..

돋보기를 누르면 요렇게 쓩 하고 나오게 된다.

저 검색창에 검색을 하게 되면

이렇게 Movie, Tv 따로 검색 결과에 대한 작품을 보여주게 된다

Header -> 돋보기 입력창

<Search onSubmit={handleSubmit(onValid)}>
     <motion.svg
              onClick={toggleSearch}
              animate={{ x: searchOpen ? -180 : 0 }}
              transition={{ type: 'linear' }}
              fill="currentColor"
              viewBox="0 0 20 20"
              xmlns="http://www.w3.org/2000/svg"
            >
       <path
          fillRule="evenodd"
           d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
                clipRule="evenodd"
              ></path>
            </motion.svg>
            <Input
              {...register('keyword', {
                required: true,
                minLength: 2,
              })}
              placeholder="Search for movie or tv show ..."
              animate={{ scaleX: searchOpen ? 1 : 0 }}
              transition={{ type: 'linear' }}
            />
          </Search>

돋보기 이미지를 svg 형태로 가져온 뒤, motion을 주어서 animation을 주었다.
-- > 사용자가 검색창을 토글할 때 돋보기 아이콘과 검색 입력 상자의 애니메이션을 보여준다.
돋보기 아이콘은 클릭 시 X 축으로 이동하여 왼쪽으로 이동하고, 입력 상자는 나타나거나 숨겨지는 효과를 보여주게 된다.

Search API

export function getSearchs(keyword: string) {
  return fetch(
    `${BASE_PATH}/search/multi?query=${keyword}&include_adult=false&language=ko-KR&api_key=${API_KEY}&page=1`
  ).then((response) => response.json());
}

Search api는 Keyword를 받아오게 된다.
여기서 keyword는 ? --> movie, tv를 나타낸다

Search

const Search = () => {
  const location = useLocation();
  
 ...
 
  const keyword = new URLSearchParams(location.search).get('keyword');

  const { data, isLoading } = useQuery<IGetSearchResult>(
    ['search', keyword],
    () => getSearchs(keyword || '')
  );

  const tvData = data?.results.filter(
    (item: ISearch) => item.media_type === 'tv'
  );

  const movieData = data?.results.filter(
    (item: ISearch) => item.media_type === 'movie'
  );

  const [videoData, setVideoData] = useState<IGetVideosResult | null>(null);

  const clickedMovie =
    bigMovieMatch?.params.movieId &&
    data?.results.find(
      (movie) => movie.id + '' === bigMovieMatch.params.movieId
    );
  const clickedTv =
    bigTvShowMatch?.params.tvId &&
    data?.results.find((tv) => tv.id + '' === bigTvShowMatch.params.tvId);

  const onOverlayClicked = () => {
    navigate(`/search?keyword=${keyword}`);
  };

  return (
    <>
      {isLoading ? (
        <Loading />
      ) : (
        <Wrapper>
          <SectionTitle>{keyword} 검색 결과를 찾았어요!</SectionTitle>

          {data && (
            <>
              <SearchSlider title="Movie" search={movieData || []} />
              <SearchSlider title="Tv" search={tvData || []} />
            </>
          )}

         //다른 BigMovie 코드와 비슷함(설명 생략)
        </Wrapper>
      )}
    </>
  );
};

export default Search;

먼저, getSearch() 함수에 keyword를 넘겨주어야 하는데, 어디서 가져와야 하지 ?

--> const keyword = new URLSearchParams(location.search).get('keyword'); 를 통해서 URLSearchParams 객체를 이용하여 현재 페이지 URL의 쿼리 파라미터에서 'keyword' 값을 추출한다.

이렇게 추출한 값을 getSearch(keyword) 넘겨주면 된다.

 const tvData = data?.results.filter(
    (item: ISearch) => item.media_type === 'tv'
  );

  const movieData = data?.results.filter(
    (item: ISearch) => item.media_type === 'movie'
  );

를 통해서 알 수 있듯이, getSearch()로 가져온 data 안에는 tv와 movie가 있어 tvData와 movieData를 따로 받아왔으며, 이 정보를 <SearchSlider/>에게 넘겨주었다!

SearchSlider

const SearchSlider = ({ title, search }: ISearchSliderProps) => {
  const location = useLocation();
  const navigate = useNavigate();
  const keyword = new URLSearchParams(location.search).get('keyword');

  const [index, setIndex] = useState(0);
  const [direction, setDirection] = useState(true);

  const maxItemsToShow = 6;

  const increaseIndex = () => {
    if (search) {
      const totalMovies = search.length;
      const maxIndex = Math.floor(totalMovies / maxItemsToShow) - 1;

      if (totalMovies <= maxItemsToShow) {
        return;
      }
      setDirection(true);
      setIndex((prevIndex) => (prevIndex === maxIndex ? 0 : prevIndex + 1));
    }
  };

  const decreaseIndex = () => {
    if (search) {
      const totalMovies = search.length;
      const maxIndex = Math.ceil(totalMovies / maxItemsToShow) - 1;

      setDirection(false);
      setIndex((prevIndex) => (prevIndex === 0 ? maxIndex : prevIndex - 1));
    }
  };

  const onBoxClicked = (itemId: number, mediaType: string) => {
    navigate(`/search/${mediaType}/${itemId}?keyword=${keyword}`);
  };

  return (
    <>
      <Section>
        <TitleArea>
          <SectionTitle>{title}</SectionTitle>
          <ButtonWrapper>
            <Button onClick={decreaseIndex}>이전</Button>
            <Button onClick={increaseIndex}>다음</Button>
          </ButtonWrapper>
        </TitleArea>
        <AnimatePresence initial={false} custom={direction}>
          <Row
            key={index}
            variants={rowVariants}
            initial="hidden"
            animate="visible"
            exit="exit"
            custom={direction}
            transition={{ type: 'tween', duration: 1 }}
          >
            {search
              .slice(
                index * maxItemsToShow,
                index * maxItemsToShow + maxItemsToShow
              )
              .map((item: ISearch) => (
                <Box
                  transition={{ type: 'tween' }}
                  variants={BoxVariants}
                  whileHover="hover"
                  initial="normal"
                  exit="exit"
                  layoutId={item.id + ''}
                  key={item.id}
                  bgPhoto={makeImagePath(item.backdrop_path)}
                  onClick={() => onBoxClicked(item.id, item.media_type)}
                />
              ))}
          </Row>
        </AnimatePresence>
      </Section>
    </>
  );
};

export default SearchSlider;

<SearchSlider title="Movie" search={movieData || []} />
<SearchSlider title="Tv" search={tvData || []} /> 이렇게 넘겨 받은
search 를 통해서 화면에 뿌려주면 된다.

search를 통해서 Box를 구성하는데(movie, tv) 여기서 누른 영화 혹은 tv의 상세 정보를 얻기 위해, onClick 함수를 통해서 누른 item의 id와 media_type을 넘겨준다.

 const onBoxClicked = (itemId: number, mediaType: string) => {
    navigate(`/search/${mediaType}/${itemId}?keyword=${keyword}`);
  };

여기서 살펴볼 수 있듯이 mediaType이 movie라면, movie로 링크가 열릴 것이고, tv라면 tv로 열릴 것이다!

Search를 마무리 하면서

거의 이틀 걸렸던 것 같다
데이터를 불러오는 것부터 조금 고생을 하고,

처음에는 Movie따로 검색을 불러오고, 화면에 뿌려주고, 영화를 보여주고 했는데 그러다보니 코드도 너무 길어지고 진짜 한 600은 넘은 것 같아서

Movie, tv를 동시에 불러오고, mediaType으로 데이터를 나눌 수 (?) 있도록 해보면서 코드를 분리하고 줄여보았다..

사실 엄청 맘에 들진 않지만, 이정도면 어느정도 깔끔해진 것 같아서.. 나름 만족 !! 🤣

profile
2024. 01. 02 ~ 백앤드 공부 시작, 2024. 04.01 ~ 프론트 공부 시작

0개의 댓글