본문 바로가기

JavaScript/React JS

[React JS] 간단한 Movie App 만들기2

이번엔 기존 기능에서 title을 클릭 시 세부 정보를 알 수 있는 기능을 추가하려고 한다.

이를 위해서는 react-router-dom 설치가 필요하다. (React JS에서 router기능을 수행하게 만들어준다.)

현재 설정

routes/Home.js ↓

import { useEffect, useState } from "react";
import Movie from "../components/Movie";

const Home = () => {
  const [loading, setLoading] = useState(true);
  /* [1,2] 1이 배열의 데이터, 2가 배열 데이터를 수정하는 함수 */
  const [movies, setMovies] = useState([]);
  const getMovies = async () => {
    const json = await (
      await fetch(
        `https://yts.mx/api/v2/list_movies.json?minimum_rating=9&sort_by=year`
      )
    ).json();
    setMovies(json.data.movies);
    setLoading(false);
  };
  useEffect(() => {
    getMovies();
  }, []);
  return (
    <div>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        <div>
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              coverImage={movie.medium_cover_image}
              title={movie.title}
              summary={movie.summary}
              genres={movie.genres}
            />
          ))}
        </div>
      )}
    </div>
  );
};

export default Home;

routes/Detail.js ↓

const Detail = () => {
  return <h1>Detail</h1>;
};

export default Detail;

components/Movie.js ↓

import propTypes from "prop-types";

const Movie = ({ coverImage, title, summary, genres }) => {
  return (
    <div>
      <img src={coverImage} alt={title} />
      <h2>{title}</h2>
      <p>{summary}</p>
      <ul>{genres == null ? "" : genres.map((i) => <li key={i}>{i}</li>)}</ul>
    </div>
  );
};

Movie.propTypes = {
  coverImage: propTypes.string.isRequired,
  title: propTypes.string.isRequired,
  summary: propTypes.string.isRequired,
  genres: propTypes.arrayOf(propTypes.string).isRequired,
  /* genres는 배열이기 때문에 arrayOf를 넣어준다 */
};

export default Movie;

로 설정하였다. 이제 router을 App.js에서 사용하려고 한다.

App.js ↓

import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./routes/Home";
import Detail from "./routes/Detail";

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/movie">
          <Detail />
        </Route>
        <Route path="/">
          <Home />
        </Route>
      </Switch>
    </Router>
  );
}

export default App;

먼저 Router, Switch, Route를 import해준 다음, 상기 구문처럼 작성한다. Switch는 현재 버전에서는 Routes로 변경되었다고 한다.

Route옆에 path는 해당 주소를 나타내며, "/"는 기본 주소이기 때문에 Home.js로 연결시켜준다.(당연히 import 필요)

또 다른 path에는 "/movie"를 설정해서 title을 클릭 시 해당 Url(Detail.js)로 이동시켜준다.

 

이제 home과 detail을 연결시켜줘야 하는데 기존 html 같이 <a href...>를 사용하면 페이지 전체가 새로고침이 되기 때문에 사용하지 않고, 새로고침 되지 않는 상태에서 화면만 전환시키는 react-router-dom의 link를 사용할 것이다.

 

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

const Movie = ({ coverImage, title, summary, genres }) => {
  return (
    <div>
      <img src={coverImage} alt={title} />
      <h2>
        <Link to="/movie">{title}</Link>
      </h2>
      <p>{summary}</p>
      <ul>{genres == null ? "" : genres.map((i) => <li key={i}>{i}</li>)}</ul>
    </div>
  );
};

다음과 같이 Link를 import 한 다음, <Link to=""></Link>를 이용해서 작성한다.

링크를 통해서 각각 다른 movie로 들어가게 되는데, 그것을 구분하기 위해 ~~/movie/숫자 와 같이 서로 다른 숫자(id)가 나오게 설정해야 한다. 그래서 Home.js에 key값인 movie.id를 id라는 prop으로 새로 추가한다. ↓

            <Movie
              key={movie.id}
              id={movie.id}
              coverImage={movie.medium_cover_image}
              title={movie.title}
              summary={movie.summary}
              genres={movie.genres}
            />

그다음 Movie.js에서 id를 import 한 다음 ↓

<Link to={`/movie/${id}`}>{title}</Link>

이렇게 바꿔준다. 그러면 개별적인 영화의 id 숫자로 들어가지게 된다.

해당 영화 title을 클릭하면

movie/37384라는 해당 movie의 id넘버로 url이 변경된다.

 

이제 :id의 값을 인지하고 URL을 변환해주는 것은 react-router-dom의 useParams이다. 이것을 이용해서 url의 변숫값을 알 수 있게 만들기로 한다.

Detail.js에서 다음과 같이 작성한다. ↓

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

const Detail = () => {
  const { id } = useParams();
  console.log(id);
  return <h1>Detail</h1>;
};

export default Detail;

const명을 id로 정한다음 useParams함수를 사용하면

이렇게 해당 url의 id값이 나오게된다.

이제 fetch를 불러와서 해당 값에 적용시켜보도록 한다. ↓

  const { id } = useParams();
  const getMovie = useCallback(async () => {
    const json = await (
      await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
    ).json();
  }, [id]);
  useEffect(() => {
    getMovie();
  }, [getMovie]);

위와 같이 만들어준다. useCallback을 하지 않으면 warning이 나오게 되므로 사용한다.

https://kyounghwan01.github.io/blog/React/exhaustive-deps-warning/#_1-useeffect내-state를-넣어줌

 

React.js - exhaustive-deps-warning, react, react-hook

React.js - exhaustive-deps-warning, react, react-hook

kyounghwan01.github.io

해당 링크는 앞서 언급한 경고가 나오게 되었을 때 해결방안이다. 필자는 useCallback을 사용하였다.

Detail.js ↓

  const [loading, setLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const { id } = useParams();
  const getMovie = useCallback(async () => {
    const json = await (
      await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
    ).json();
    console.log([json.data.movie]);
    /* 그냥 json.data.movie로 하면 object이기 때문에 map함수가 적용안됨. map은 arrary에서 적용 */
    setMovies([json.data.movie]);
    setLoading(false);
  }, [id]);
  useEffect(() => {
    getMovie();
  }, [getMovie]);

Callback함수, 영화의 상세내용을 담고있는 api url, Home.js와 같이 loading 및 data arrary 생성.

위에도 작성하였지만, 이번에 불러온 detail data는 arrary가 아닌 string형태이기 때문에 []로 감싸줘야 배열 형태가 된다.

 

이후 내용들은

component/MovieDetail.js를 만들어서 data를 보냈다.

Detail.js ↓

  return loading ? (
    <h1>Loading...</h1>
  ) : (
    <div>
      {movies.map((movie) => (
        <MovieDetail
          key={movie.id}
          title={movie.title}
          bkImage={movie.background_image_original}
          longTitle={movie.title_long}
          summary={movie.description_full}
          rating={movie.rating}
          runtime={movie.runtime}
        />
      ))}
    </div>
  );

component/MovieDetail.js  ↓

import propTypes from "prop-types";
import { Link } from "react-router-dom";

const MovieDetail = ({
  title,
  bkImage,
  longTitle,
  summary,
  rating,
  runtime,
}) => {
  return (
    <div>
      <div>
        <h2>
          <Link to={`/`}>Home</Link>
        </h2>
      </div>
      <img alt={title} src={bkImage}></img>
      <h1>{longTitle}</h1>
      <p>{summary}</p>
      <div>Rating : {rating}/10</div>
      <div>Runtime : {runtime}min</div>
    </div>
  );
};

MovieDetail.propTypes = {
  bkImage: propTypes.string.isRequired,
  title: propTypes.string.isRequired,
  longTitle: propTypes.string.isRequired,
  summary: propTypes.string.isRequired,
  rating: propTypes.number.isRequired,
  runtime: propTypes.number,
};

export default MovieDetail;

결과1
결과2

이제 css를 적용할건데, React JS에서 css를 적용하기 좋은 방법이 있다.

다음과 같이 Movie.module.css파일을 만들어준다.

해당 css적용이 될 Movie.js에서는 

import styles from "../css/Movie.module.css";

로 적용시킨다.

css 파일에서 title의 back ground color을 변경하려고 한다.

.title {
  background-color: tomato;
}

다음과 같이 작성한 후

다시 Movie.js에서

      <h2>
        <Link to={`/movie/${id}`} className={styles.title}>
          {title}
        </Link>
      </h2>

이렇게 작성하는데, 우리는 import할 때 styles라고 했으므로 className에 styles.title 로 작성하면

css에서 .title로 작성한 부분이 적용된다.

css 적용 화면