Frontend πŸ“š/React

[React] TMDB APIλ₯Ό ν™œμš©ν•œ λ„·ν”Œλ¦­μŠ€ 클둠 μ½”λ”© - 6. 검색 κΈ°λŠ₯ κ΅¬ν˜„ 및 찜 νŽ˜μ΄μ§€ κ΅¬ν˜„

leejaejae 2024. 12. 8. 17:59

μ˜ν™” 검색 κΈ°λŠ₯ 및 찜 νŽ˜μ΄μ§€ κ΅¬ν˜„

이번 ν¬μŠ€νŒ…μ—μ„œλŠ” 찜 νŽ˜μ΄μ§€μ™€ μ˜ν™” 검색 κΈ°λŠ₯을 κ΅¬ν˜„ν•œ λ‚΄μš©μ„ λ‹€λ€„λ³΄κ² μŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 μ‚¬μš©μžκ°€ μ‚¬μ΄νŠΈμ—μ„œ 찜 λͺ©λ‘μ„ μ‰½κ²Œ λ³Ό 수 있고 μ˜ν™” 검색을 효율적으둜 ν•  수 μžˆλ„λ‘ λ§Œλ“œλŠ” 방법을 μ†Œκ°œν•©λ‹ˆλ‹€.


1. μ˜ν™” 검색 κΈ°λŠ₯ κ΅¬ν˜„

μ‚¬μš©μžλŠ” μ˜ν™” 검색 κΈ°λŠ₯을 톡해 μ›ν•˜λŠ” μ˜ν™”λ₯Ό λΉ λ₯΄κ²Œ 찾을 수 μžˆμŠ΅λ‹ˆλ‹€. 이 κΈ°λŠ₯은 TMDB APIλ₯Ό ν™œμš©ν•˜μ—¬ κ²€μƒ‰λœ μ˜ν™” λͺ©λ‘μ„ ν‘œμ‹œν•˜κ³ , 필터링 μ˜΅μ…˜μ„ μ œκ³΅ν•˜μ—¬ μ‚¬μš©μžκ°€ μ›ν•˜λŠ” μ˜ν™”λ₯Ό 정리할 수 μžˆλ„λ‘ λ•μŠ΅λ‹ˆλ‹€.

1.1 검색어 μž…λ ₯ 및 API 호좜

μ‚¬μš©μžκ°€ 검색어λ₯Ό μž…λ ₯ν•˜λ©΄ ν•΄λ‹Ή 검색어λ₯Ό λ°”νƒ•μœΌλ‘œ TMDB APIμ—μ„œ μ˜ν™” λͺ©λ‘μ„ λ°›μ•„μ˜΅λ‹ˆλ‹€. 이λ₯Ό μœ„ν•΄ useState둜 검색어 μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜κ³ , 검색 λ²„νŠΌμ„ ν΄λ¦­ν•˜λ©΄ searchMovies ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜μ—¬ API μš”μ²­μ„ λ³΄λƒ…λ‹ˆλ‹€.

searchPage.js

//pages/searchPage.js
const handleSearch = useCallback(async () => {
  try {
    const data = await searchMovies(query, page);
    setMovies(data.results);
    setTotalPages(data.total_pages);
  } catch (error) {
    console.error("Error fetching search results:", error);
  }
}, [query, page]);

1.2 필터링 κΈ°λŠ₯

μ‚¬μš©μžλŠ” 검색 κ²°κ³Όλ₯Ό μž₯λ₯΄, 평점, μ •λ ¬ λ°©μ‹μœΌλ‘œ 필터링할 수 μžˆμŠ΅λ‹ˆλ‹€. 필터링은 filters 객체λ₯Ό 톡해 κ΄€λ¦¬λ˜λ©°, μž₯λ₯΄μ™€ 평점 등을 κΈ°μ€€μœΌλ‘œ μ˜ν™”λ₯Ό 선택할 수 μžˆμŠ΅λ‹ˆλ‹€. ν•„ν„°λ§λœ μ˜ν™” λͺ©λ‘μ€ filteredMovies μƒνƒœμ— μ €μž₯λ˜μ–΄ μ‚¬μš©μžμ—κ²Œ λ³΄μ—¬μ§‘λ‹ˆλ‹€.

//searchPage.js
const applyFilters = useCallback(() => {
  let updatedMovies = [...movies];

  if (filters.genre) {
    updatedMovies = updatedMovies.filter((movie) =>
      movie.genre_ids.includes(parseInt(filters.genre))
    );
  }

  if (filters.rating > 0) {
    updatedMovies = updatedMovies.filter(
      (movie) => movie.vote_average >= filters.rating
    );
  }

  if (filters.sortBy) {
    updatedMovies.sort((a, b) => {
      if (filters.sortBy === "popularity.desc") {
        return b.popularity - a.popularity;
      } else if (filters.sortBy === "release_date.desc") {
        return new Date(b.release_date) - new Date(a.release_date);
      }
      return 0;
    });
  }

  setFilteredMovies(updatedMovies);
}, [movies, filters]);

1.3 νŽ˜μ΄μ§€λ„€μ΄μ…˜

검색 κ²°κ³ΌλŠ” νŽ˜μ΄μ§€λ„€μ΄μ…˜μ„ 톡해 μ—¬λŸ¬ νŽ˜μ΄μ§€μ— 걸쳐 ν‘œμ‹œλ©λ‹ˆλ‹€. μ‚¬μš©μžκ°€ νŽ˜μ΄μ§€λ₯Ό λ³€κ²½ν•  λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ 검색 κ²°κ³Όλ₯Ό λΆˆλŸ¬μ˜΅λ‹ˆλ‹€. 이λ₯Ό μœ„ν•΄ Pagination μ»΄ν¬λ„ŒνŠΈλ₯Ό μ‚¬μš©ν•˜μ—¬ ν˜„μž¬ νŽ˜μ΄μ§€μ™€ 전체 νŽ˜μ΄μ§€ 수λ₯Ό μ „λ‹¬ν•˜κ³ , νŽ˜μ΄μ§€λ₯Ό λ³€κ²½ν•  수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.

<Pagination page={page} totalPages={totalPages} setPage={setPage} />

Pagination.js

//components/Pagination.js
import React from "react";

const Pagination = ({ page, totalPages, setPage }) => {
  const handlePrevPage = () => {
    if (page > 1) {
      setPage(page - 1);
    }
  };

  const handleNextPage = () => {
    if (page < totalPages) {
      setPage(page + 1);
    }
  };

  return (
    <div className="pagination">
      <button onClick={handlePrevPage} disabled={page === 1}>
        β—€οΈŽ
      </button>
      <span>
        {page} / {totalPages}
      </span>
      <button onClick={handleNextPage} disabled={page === totalPages}>
        β–ΆοΈŽ
      </button>
    </div>
  );
};

export default Pagination;

2. 찜 νŽ˜μ΄μ§€ κ΅¬ν˜„

WishlistPage.js

//pages/WishlistPage.js
const WishlistPage = () => {
  const [wishlist, setWishlist] = React.useState([]);
  
  ...
  // μ°œν•œ μ˜ν™” λͺ©λ‘μ„ localStorageμ—μ„œ κ°€μ Έμ˜€κΈ°
  React.useEffect(() => {
    const storedWishlist = getFromLocalStorage("favoriteMovies") || []; // localStorageμ—μ„œ 찜 λͺ©λ‘ κ°€μ Έμ˜€κΈ°
    setWishlist(storedWishlist);
  }, []);

  // 찜 λͺ©λ‘μ—μ„œ μ˜ν™” μ‚­μ œ
  const handleRemoveFromWishlist = (movie) => {
    const updatedWishlist = wishlist.filter((item) => item.id !== movie.id); // μ‚­μ œν•  μ˜ν™” μ œμ™Έν•œ λͺ©λ‘
    setWishlist(updatedWishlist); // μƒνƒœ μ—…λ°μ΄νŠΈ
    saveToLocalStorage("favoriteMovies", updatedWishlist); // localStorage에 찜 λͺ©λ‘ μ €μž₯
  };

  ...
  return (
    <div className="wishlist-page">
      <h1>μ°œν•œ μ½˜ν…μΈ </h1>
      {wishlist.length > 0 ? (
        <div className="movie-list">
          {wishlist.map((movie) => (
            <MovieCard
              key={movie.id}
              movie={movie}
              onClick={() => openModal(movie)}
            />
          ))}
        </div>
      ) : (
        <p>μ°œν•œ μ˜ν™”κ°€ μ—†μŠ΅λ‹ˆλ‹€.</p>
      )}

      {showModal && selectedMovie && (
        <Modal
          movie={selectedMovie}
          onClose={closeModal}
          handleAddToWishlist={() => {}}
          handleRemoveFromWishlist={handleFavoriteAction} // 찜 μ·¨μ†Œ ν•¨μˆ˜ 전달
          isFavorite={true} // 찜 μƒνƒœλŠ” 항상 true둜 μ„€μ • (μ°œν•œ μƒνƒœ)
        />
      )}
    </div>
  );
 };

export default WishlistPage;

πŸš€ κ²°κ³Ό ν™”λ©΄

  • μ˜ν™” 검색 κΈ°λŠ₯

 

  • 찜 νŽ˜μ΄μ§€

πŸ’‘ 정리

이번 ν¬μŠ€νŒ…μ—μ„œλŠ” 찜 νŽ˜μ΄μ§€μ™€ μ˜ν™” 검색 κΈ°λŠ₯을 κ΅¬ν˜„ν•˜λŠ” 방법을 μ‚΄νŽ΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. 찜 νŽ˜μ΄μ§€λ₯Ό 톡해 μ‚¬μš©μžκ°€ μ°œν•œ μ˜ν™” λͺ©λ‘μ„ μ†μ‰½κ²Œ 확인할 수 있고, μ˜ν™” 검색 κΈ°λŠ₯μ—μ„œλŠ” TMDB APIλ₯Ό ν™œμš©ν•΄ μ‚¬μš©μžκ°€ μ›ν•˜λŠ” μ˜ν™”λ₯Ό κ²€μƒ‰ν•˜κ³ , 필터링할 수 μžˆλ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.