이번 프로젝트는 유튜버 '인브로즈'님의 강의를 참고해서 만들었다.

처음 영상을 접했을 때는 '촬쓰'? 라는 이름으로 활동하셨던 기억이 있는데, 강의를 본격적으로 하기 위해 활동명을 변경하신 모양이다.

 

이번 프로젝트는 useState, useEffect, useParams, axios, react-router-dom 등을 이용하였다.

 


완성본 - 리스트
완성본 - 디테일 페이지


import './CSS/index.css';
import { Routes, Route } from 'react-router-dom';
import { useState, useEffect } from 'react';
import axios from 'axios';
// import components
import MainNavbar from './components/MainNavbar';
// import pages
import Home from './pages/Home';
import NowPlaying from './pages/NowPlaying';
import Detail from './pages/Detail';

export default function App() {

  const [movie, setMovie] = useState();

  function getDatas() {
    
    const API_KEY = 'API 키코드';
    const url = `https://api.themoviedb.org/3/movie/now_playing?api_key=${API_KEY}&language=ko-KR&page=1&region=KR`
    axios.get(url).then((response) => {
      setMovie(response.data.results);
    }).catch(() => {
      console.log('데이터 접근에 실패하였습니다.');
    })
  }

  useEffect(() => {
    getDatas()
  },[])
  
  return (
    <div id='main-container' className='containers'>
      <MainNavbar />
      <Routes>
        <Route path='/' element={<Home />}/>
        <Route path='/nowplaying' element={<NowPlaying 
                                             movie={movie}/>}/>
        <Route path='/detail/:id' element={<Detail 
                                              movie={movie}/>} />
      </Routes>
    </div>
  )
}

 

메인 페이지 코드

 

전체 라우터를 세팅해주었다.

그 후 useEffect Hook을 이용해 페이지가 마운트 되었을 때 axios를 통해 TMDB API 서버의 정보를 가져올 수 있도록 하였다. 

강의 영상에서는 단순히 url 내부의 코드를 js파일에 저장해서 정적으로 사용했는데 나는 axios 연습도 해볼 겸 서버에서 직접 데이터를 요청하는 작업을 선택하였다.

 


import { useNavigate } from "react-router-dom";


const Cards = ({movie, i}) => {

  const navigate = useNavigate();
  
  const BASE_URL = 'https://image.tmdb.org/t/p/w500/'
  const image_path = BASE_URL + movie[i].poster_path;
  
  return(
    <div className='cards' onClick={() => {navigate(`./../detail/${movie[i].original_title}`)}}>
      <img src={image_path} alt='영화포스터 입니다.'/>
      <div className='cards-description'>
        <span className='cards-title'>{movie[i].title}</span>
        <span className='cards-vote'>{movie[i].vote_average}</span>
      </div>
    </div>
  )
}

export default Cards;

영화 리스트에서 포스터를 보여주는 코드이다.

 

컴포넌트로 빼서 리스트 페이지에 넣고 map함수로 화면에 뿌려주었다.

 

useNavigate 기능을 사용. 무비 리스트 페이지의 각 영화 포스터를 클릭 시 디테일 페이지로 넘어갈 수 있도록 설정해 주었다.

 

나머지는 요청한 데이터를 기반으로 내용이 입력될 수 있도록 메인페이지로부터 movie라는 배열 데이터를 props로 가져와 활용하였다.

 


import { useParams } from 'react-router-dom';
import { useState, useEffect } from 'react';
import axios from 'axios';

const Detail = ({ movie }) => {
  const { id } = useParams();
  const [detailData, setDetailData] = useState();
  const [genres, setGenres] = useState([]);

  useEffect(() => {
    const API_KEY = '6207740ea0550aece9d7ae1d7e6d97f1';
    const url = `https://api.themoviedb.org/3/genre/movie/list?api_key=${API_KEY}&language=ko-KR`;
    axios.get(url)
      .then((response) => {
        setGenres(response.data.genres);
      })
      .catch(() => {
        console.log('데이터 요청 실패');
      });
  }, []);

  useEffect(() => {
    if (movie && genres.length > 0) {
      const found = movie.find((a) => a.original_title === id);
      setDetailData(found);
    }
  }, [id, movie, genres]);

  return (
    <div id='detail-container' className='containers'>
      {detailData ? (
        <div className='detail-wrapper'>
          <div className='detail-header'>
            <h2 className='detail-title'>{detailData.title}</h2>
            <p className='detail-rating'>{detailData.vote_average} / 10</p>
          </div>
          <div className='detail-content'>
            <img
              className='detail-poster'
              src={`https://image.tmdb.org/t/p/w500${detailData.poster_path}`}
              alt={detailData.original_title}
            />
            <div className='detail-info'>
              <p className='detail-overview'>{detailData.overview}</p>
              <p className='detail-release'>
                원제: {detailData.original_title}
              </p>
              <p className='detail-runtime'>
                개봉일: {detailData.release_date}
              </p>
              <p className='detail-genre'>
                장르: {detailData.genre_ids.map((id) => {
                  const genre = genres.find((g) => g.id === id);
                  return genre ? genre.name : '';
                }).filter((name) => name).slice(0, 3).join(', ')}
              </p>
            </div>
          </div>
        </div>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

export default Detail;

디테일 페이지 코드 내용.

 

useParams Hook을 이용해 url 뒷부분에 각 영화의 원제목을 표기하게 했다.

그 후에 find 함수로 movie 배열 중 해당 페이지의 영화 원제목을 갖고 있는 요소만 걸러냈다. 그리고는 내용 화면에 뿌려주기.

 

** 이번 프로젝트 중 가장 어려웠던 부분은 영화의 장르를 가져오는 내용이었다.

강의에는 장르까지 다루지 않아서 혼자 열심히 씨름했다 ㅋㅋ..

 

영화의 장르 데이터는 위의 사진과 같이 스트링으로 표기되어있지 않다. 때문에 일련의 작업이 필요하다.

먼저, axios를 통해 genres 리스트를 받아오고,

<p className='detail-genre'>
                장르: {detailData.genre_ids.map((id) => {
                  const genre = genres.find((g) => g.id === id);
                  return genre ? genre.name : '';
                }).filter((name) => name).slice(0, 3).join(', ')}
              </p>

해당 작업을 통해 디테일 페이지에 최대 3개까지 영화의 장르를 표시할 수 있게 된다.

이 몇 줄 안 되는 코드에 map, find, filter, slice, join ... 배열에 관련된 함수만 다섯 개가 쓰인다.

이 코드가 어려운 건 당연히 js 숙련도가 낮기 때문,, 지금은 react가 아니라 js 기초를 더 다지는 게 맞는 걸까..라고 생각도 들었다.

 


https://github.com/mediumryan/TMDB_basic