핵심 기능인 MovieList, 영화 데이터를 불러오고 리스트로 나열한다.
영화 제목을 받는 input
, 영화 평점 option
평점은 대체로 낮아서 주로 선택하지 않고 검색하긴 한다.
영화 검색 후 list 가 나열되고 옆으로 스크롤 가능하게 만들었다.
import axios from "axios";
import { useEffect } from "react";
import { useState } from "react";
import "./list.css";
import { Navigate, useNavigate, useSearchParams } from "react-router-dom";
import { API_KEY, IMG_BASE_URL } from "../constant";
const MoviePage = () => {
const [keyword, setKeyword] = useState("");
const [movieList, setMovieList] = useState([]);
const [rating, setRating] = useState(0);
const navigate = useNavigate();
useEffect(() => {
console.log("movieList", movieList);
}, [movieList]);
const handleSubmitKeyword = async (e) => {
e.preventDefault();
if (keyword.trim() === "") {
alert("검색어를 입력해주세요.");
return;
}
const { data } = await axios
.get(`https://api.themoviedb.org/3/search/movie?query=${keyword}`, {
headers: {
Authorization: API_KEY,
},
})
.catch((e) => {
console.log(e);
});
setMovieList(data.results);
};
const handleChangeKeyword = (e) => {
setKeyword(e.target.value);
};
const handleChangeRating = (e) => {
setRating(e.target.value);
};
return (
<div className="page_container">
<h1 className="page_title">Search the Movie</h1>
<div className="list_search_container">
<form
onSubmit={handleSubmitKeyword}
className="search_option_container"
>
<input
value={keyword}
onChange={handleChangeKeyword}
placeholder="영화 제목"
className="search_keyword"
/>
<select
value={rating}
onChange={handleChangeRating}
className="search_rating"
>
<option value={0}>선택안함</option>
<option value={7.0}>7.0 이상</option>
<option value={7.5}>7.5 이상</option>
<option value={8.0}>8.0 이상</option>
<option value={8.5}>8.5 이상</option>
<option value={9.0}>9.0 이상</option>
</select>
<button type="submit" className="search_submit">
검색
</button>
</form>
</div>
<div className="movie_list_container">
<ul className="movie_list">
{movieList.map(
({ id, title, poster_path, release_date, vote_average }, index) => {
if (rating <= vote_average)
return (
<li key={`${id}_${index}`} className="movie_list_item">
<div className="contents_container">
<div className="list_title">{title}</div>
{poster_path ? (
<img
className="list_img"
src={`${IMG_BASE_URL}${poster_path}`}
onClick={() => {
navigate(`/detail/${id}`);
}}
/>
) : (
<div className="list_no_img">이미지 없음</div>
)}
<div className="list_release_date">
출시일 : {release_date}
</div>
<div className="list_rating">평점 : {vote_average}</div>
</div>
</li>
);
}
)}
</ul>
</div>
</div>
);
};
export default MoviePage;
useState
와 useEffect
을 사용했다.
먼저, useState
는
const [state, setState] = useState(initialState);
상태 유지 값과 그 값을 갱신하는 함수를 반환합니다.
최초로 렌더링을 하는 동안, 반환된 state(state)는 첫 번째 전달된 인자(initialState)의 값과 같습니다. setState 함수는 state를 갱신할 때 사용합니다. 새 state 값을 받아 컴포넌트 리렌더링을 큐에 등록합니다. - react 공식문서
간단하게 말해서 상태를 저장할 수 있는거다. list를 나열할때 우리가 상태를 저장해야 하는 것은 keyword 와 평점, 그리고 받아온 데이터를 저장하는 list가 필요하다. API 마다 조건이 다르겠지만 이 곳에서는 기본 링크 뒤에 keyword를 query로 사용한다. 따라서 input
에서 받아온 keyword를 State에 저장하고 평점은 선택된 option
값에 따라서 Rendering에서 조건문으로 return 해준다. 처음에 불러온 데이터의 형태를 확인하면 객체 형태인걸 알 수 있다. 데이터를 확인하고 싶다면 네트워크 > Fetch/XHR > 미리보기에서 확인 가능하지만 바로 콘솔에 띄우고 싶다면 useEffect를 사용하는 것이 편하다. 받아온 데이터의 형태가 많고 객체형태로 있었기에 list를 화면에 띄우려면 배열로 만들어 .map
을 돌려야 했다. 따라서 movieList
에 초기값은 []
이다.
*useEffect
ffect는 종종 컴포넌트가 화면에서 제거될 때 정리해야 하는 리소스를 만듭니다. 가령 구독이나 타이머 ID와 같은 것입니다. 이것을 수행하기 위해서 useEffect로 전달된 함수는 정리(clean-up) 함수를 반환할 수 있습니다. 예를 들어 구독을 생성하는 경우는 아래와 같습니다.
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// Clean up the subscription
subscription.unsubscribe();
};
});
정리 함수는 메모리 누수 방지를 위해 UI에서 컴포넌트를 제거하기 전에 수행됩니다. 더불어, 컴포넌트가 (그냥 일반적으로 수행하는 것처럼) 여러 번 렌더링 된다면 다음 effect가 수행되기 전에 이전 effect는 정리됩니다. - React 공식문서
CSS
.page_container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.page_title {
display: flex;
flex-direction: column;
align-items: center;
font-size: 50px;
font-weight: 100;
}
.list_search_container {
display: flex;
flex-direction: column;
align-items: center;
}
.search_keyword {
border-radius: 5px;
border: 1px solid black;
width: 140px;
line-height: 20px;
}
.search_rating {
margin-left: 7px;
margin-right: 7px;
border: 1px solid black;
border-radius: 5px;
line-height: 20px;
}
.movie_list_container {
display: flex;
flex-direction: column;
}
.movie_list {
overflow-x: scroll;
white-space: nowrap;
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
margin-top: 20px;
}
.movie_list_item {
width: fit-content;
height: fit-content;
display: flex;
flex-direction: column;
}
.contents_container {
width: 300px;
height: 400px;
display: flex;
flex-direction: column;
align-items: center;
}
.list_img {
width: 100%;
height: 80%;
object-fit: contain;
border-radius: 8px;
overflow: hidden;
padding: 6px;
}