ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React - Create Detail Container and Presenter
    Project using React/Cloning Netflix 2021. 5. 9. 11:12

    Components/Poster.js

    import styled from "styled-components";
    import PropTypes from "prop-types";
    import { Link } from "react-router-dom";
    
    const Container = styled.div``;
    
    const Image = styled.div`
      background-image: url(${(props) => props.bgUrl});
      width: 100%;
      height: 180px;
      background-repeat: no-repeat;
      background-size: cover;
      background-position: center;
      transition: opacity 0.1s linear;
    `;
    
    const Rating = styled.span`
      bottom: 5px;
      right: 5px;
      position: absolute;
      opacity: 0;
      transition: opacity 0.1s linear;
    `;
    
    const ImageContainer = styled.div`
      position: relative;
      &:hover {
        ${Image} {
          opacity: 0.3;
        }
        ${Rating} {
          opacity: 1;
        }
      }
    `;
    
    const Title = styled.span`
      display: block;
      margin-bottom: 3px;
    `;
    
    const Year = styled.span`
      font-size: 10px;
      color: rgba(255, 255, 255, 0.5);
    `;
    
    const Poster = ({ id, imageUrl, title, rating, year, isMovie = false }) => (
      <Link to={isMovie ? `/movie/${id}` : `/tv/${id}`}>
        <Container>
          <ImageContainer>
            <Image
              bgUrl={
                imageUrl
                  ? `https://image.tmdb.org/t/p/w300${imageUrl}`
                  : require("../assets/noPosterSmall.png")
              }
            />
            <Rating>
              <span role="img" aria-label="rating">
                ⭐️
              </span>{" "}
              {rating}/10
            </Rating>
          </ImageContainer>
          <Title>
            {title.length > 18 ? `${title.substring(0, 18)}...` : title}
          </Title>
          <Year>{year}</Year>
        </Container>
      </Link>
    );
    
    Poster.propTypes = {
      id: PropTypes.number.isRequired,
      imageUrl: PropTypes.string,
      title: PropTypes.string.isRequired,
      rating: PropTypes.number,
      year: PropTypes.string,
      isMovie: PropTypes.bool,
    };
    
    export default Poster;
    

    Poster에 Link를 추가했다. 이제 해당 링크로 Detail page를 갈 수 있다.

    Detail/DetailContainer.js

    import React from "react";
    
    import DetailPresenter from "./DetailPresenter";
    import { moviesApi, tvApi } from "api";
    
    export default class extends React.Component {
      constructor(props) {
        super(props);
        const {
          location: { pathname },
        } = props;
        this.state = {
          result: null,
          loading: true,
          error: null,
          isMovie: pathname.includes("/movie/"),
        };
      }
    
      async componentDidMount() {
        const {
          match: {
            params: { id },
          },
        } = this.props;
        let result = null;
        const { isMovie } = this.state;
        try {
          if (isMovie) {
            ({ data: result } = await moviesApi.movieDetail(id));
          } else {
            ({ data: result } = await tvApi.showDetail(id));
          }
          this.setState({
            result,
          });
        } catch {
          this.setState({
            error: "Can't find anything.",
          });
        } finally {
          this.setState({
            loading: false,
          });
        }
      }
      render() {
        const { result, error, loading } = this.state;
        return <DetailPresenter result={result} error={error} loading={loading} />;
      }
    }
    

      ComponentDidMount()를 사용하면 this.props를 사용할 수 있다. prop에는 <Route>가 넘겨주는 location과 params이 있다. isMovie는 url을 읽고 해당 Poster가 Movie Poster인지 TV Poster인지 확인한다.

    Detail/DetailPresenter.js

    import React from "react";
    import PropTypes from "prop-types";
    import styled from "styled-components";
    import Helmet from "react-helmet";
    
    import Loader from "Components/Loader";
    
    const Container = styled.div`
      height: calc(100vh - 50px);
      width: 100%;
      position: relative;
      padding: 50px;
    `;
    
    const Backdrop = styled.div`
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-image: url(${(props) => props.bgImage});
      background-position: center center;
      background-size: cover;
      filter: blur(3px);
      opacity: 0.5;
      z-index: 0;
    `;
    
    const Content = styled.div`
      display: flex;
      width: 100%;
      position: relative;
      z-index: 1;
      height: 100%;
    `;
    
    const Cover = styled.div`
      width: 30%;
      background-image: url(${(props) => props.bgImage});
      background-position: center center;
      background-size: cover;
      height: 100%;
      border-radius: 5px;
    `;
    
    const Data = styled.div`
      width: 70%;
      margin-left: 10px;
    `;
    
    const Title = styled.h3`
      font-size: 32px;
    `;
    
    const ItemContainer = styled.div`
      margin: 20px 0;
    `;
    
    const Item = styled.span``;
    
    const Divider = styled.span`
      margin: 0 10px;
    `;
    
    const Overview = styled.p`
      font-size: 12px;
      opacity: 0.7;
      line-height: 1.5;
      width: 50%;
    `;
    
    const DetailPresenter = ({ result, loading, error }) =>
      loading ? (
        <>
          <Helmet>
            <title>Loading | Nomflix</title>
          </Helmet>
          <Loader />
        </>
      ) : (
        <Container>
          <Helmet>
            <title>
              {result.original_title ? result.original_title : result.original_name}{" "}
              | Nomflix
            </title>
          </Helmet>
          <Backdrop
            bgImage={`https://image.tmdb.org/t/p/original${result.backdrop_path}`}
          />
          <Content>
            <Cover
              bgImage={
                result.poster_path
                  ? `https://image.tmdb.org/t/p/original${result.poster_path}`
                  : require("../../assets/noPosterSmall.png")
              }
            />
            <Data>
              <Title>
                {result.original_title
                  ? result.original_title
                  : result.original_name}
              </Title>
              <ItemContainer>
                <Item>
                  {result.release_date
                    ? result.release_date.substring(0, 4)
                    : result.first_air_date.substring(0, 4)}
                </Item>
                <Divider>•</Divider>
                <Item>
                  {result.runtime ? result.runtime : result.episode_run_time[0]} min
                </Item>
                <Divider>•</Divider>
                <Item>
                  {result.genres &&
                    result.genres.map((genre, index) =>
                      index === result.genres.length - 1
                        ? genre.name
                        : `${genre.name} / `
                    )}
                </Item>
              </ItemContainer>
              <Overview>{result.overview}</Overview>
            </Data>
          </Content>
        </Container>
      );
    
    DetailPresenter.propTypes = {
      result: PropTypes.object,
      loading: PropTypes.bool.isRequired,
      error: PropTypes.string,
    };
    
    export default DetailPresenter;
    

    key

      nowPlaying, upcoming, popular는 모두 배열이다. 이들을 map을 이용해서 <Poster>를 부르고 있다. 이 때 리스트 안에 있는 <Poster>에 key 값을 추가해야만 한다.

    prop-types

    설치

    $ npm i prop-types

    개요

      prop-types는 prop의 type을 정의한다. prop을 받을 component 뒤에 method 형식으로 propTypes를 하고 객체를 정의한다.

      위 코드와 같이 type을 정의하면 MoviePresenter가 받아들이는 prop이 옳지 않은 type일 때 error를 일으켜 개발자가 실수 하는 것을 막아준다. 만약 React를 typescript로 짰다면 prop-types는 필요가 없다.

    참고 자료

    소스 코드

    github.com/zpskek/Nomflix-v2/commit/63c01c5343fe6abf64e1b0ab9590bd33d60f58d9

    'Project using React > Cloning Netflix' 카테고리의 다른 글

    Netlify에 React 배포  (0) 2021.05.09
    React - Search Container and Presenter  (0) 2021.05.09
    React - Create TV Container and Presenter  (0) 2021.05.09
    React - Create MoviePresenter  (0) 2021.05.09
    React - Create Loader.js  (0) 2021.05.09

    댓글

Designed by Tistory.