Day 02 - 220512
1. 세부 컴포넌트 작성
- MovieDetail
- MovieGroup
- MovieSearch
- MovieSlide
2. router 작성
- Home
$ yarn add prop-types
MovieDetail.js
import PropTypes from 'prop-types';
const MovieDetail = ({
coverImg,
rating,
runtime,
description_full,
title,
genres,
}) => {
return (
<div className="movie">
<div className="shortview">
<div className="shortview-img">
<img src={coverImg} alt={title} />
</div>
<div className="shortview-text">
<h3>{title}</h3>
{rating && <p>{`rating: ${rating} / 10`}</p>}
{runtime && <p>{`runtime: ${runtime} min`}</p>}
{genres && (
<div>
<h4>genres</h4>
<ul>
{genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
)}
{description_full && (
<div className="desc">
<p>{description_full}</p>
</div>
)}
</div>
</div>
</div>
);
};
MovieDetail.propTypes = {
rating: PropTypes.number,
runtime: PropTypes.number,
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description_full: PropTypes.string,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default MovieDetail;
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
const MovieGroup = ({
id,
coverImg,
title,
rating,
runtime,
year,
summary,
}) => {
return (
<div>
<div className="shortview">
<div className="shortview-img">
<img src={coverImg} alt={title} />
</div>
<div className="shortview-text">
<div className="title">
<h3>
<Link to={`/movie/${id}`}>
{title.length > 35 ? `${title.slice(0, 35)}...` : title}
</Link>
</h3>
</div>
<div className="info">
<p>{year && `year: ${year}`}</p>
<p>{rating && `rating: ${rating} / 10`}</p>
<p>{runtime && `runtime: ${runtime} min`}</p>
<p>
{summary &&
(summary.length > 180
? `${summary.slice(0, 180)}...`
: summary)}
</p>
</div>
</div>
</div>
</div>
);
};
MovieGroup.propTypes = {
id: PropTypes.number.isRequired,
img: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
rating: PropTypes.number,
runtime: PropTypes.number,
summary: PropTypes.string,
};
export default MovieGroup;
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
const MovieSearch = ({
id,
coverImg,
title,
rating,
runtime,
year,
summary,
}) => {
return (
<div>
<div className="show">
<div className="shortview">
<div className="shortview-img">
<img src={coverImg} alt={title} />
</div>
<div className="shortview-text">
<div className="title">
<h3>
<Link to={`/movie/${id}`}>
{title.length > 35 ? `${title.slice(0, 35)}...` : title}
</Link>
</h3>
</div>
<div className="info">
<p>{year && `year: ${year}`}</p>
<p>{rating && `rating: ${rating} / 10`}</p>
<p>{runtime && `runtime: ${runtime} min`}</p>
<p>
{summary &&
(summary.length > 180
? `${summary.slice(0, 180)}...`
: summary)}
</p>
</div>
</div>
</div>
</div>
</div>
);
};
MovieSearch.propTypes = {
id: PropTypes.number.isRequired,
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
rating: PropTypes.number,
runtime: PropTypes.number,
summary: PropTypes.string,
};
export default MovieSearch;
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
const MovieSlide = ({ id, coverImg, rating, runtime, title }) => {
return (
<div>
<Link to={`/movie/${id}`}>
<img src={coverImg} alt={title} />
</Link>
<div className="text">
<div className="title">
<h3>
<Link to={`/movie/${id}`}>
{title.length > 35 ? `${title.slice(0, 35)}...` : title}
</Link>
</h3>
</div>
</div>
<span>{rating && `rating: ${rating} / 10`}</span>
<span>{runtime && `runtime: ${runtime} min`}</span>
</div>
);
};
MovieSlide.propTypes = {
id: PropTypes.number.isRequired,
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
};
export default MovieSlide;
import { Link } from 'react-router-dom';
import Slide from '../components/Slide';
import { ImPlay } from 'react-icons/im';
import { Group_obj, Group_key_arr } from '../atom/NavList';
const Home = () => {
return (
<div>
{Group_key_arr.map((group) => (
<div key={group}>
<div className="title">
<Link
to={`/page/${Group_obj[group]}/1`}
style={{
display: 'flex',
flexDirection: 'row',
alignContent: 'center',
}}
>
<ImPlay />
<h3>{group}</h3>
</Link>
</div>
<Slide
movieApi={`https://yts.mx/api/v2/list_movies.json?limit=10&${Group_obj[group]}&sort_by=rating`}
/>
</div>
))}
<div className="footer">
<div className="author">
<h4>thisisyjin</h4>
</div>
<ul>
<li>githubs</li>
<li>dev log</li>
<li>contact</li>
</ul>
</div>
</div>
);
};
export default Home;
-> 여기에서 Slide 컴포넌트에 movieApi
라는 props를 전달해준다.
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import MovieDetail from '../components/MovieDetail';
import Load from '../components/Load';
const Detail = () => {
const { id } = useParams();
const [loading, setLoading] = useState(true);
const [movie, setMovie] = useState([]);
const getMovie = async () => {
const json = await (
await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
).json();
setMovie(json.data.movie);
};
useEffect(() => {
getMovie();
setLoading(false);
}, []);
return (
<div>
{loading ? (
<Load />
) : (
<MovieDetail
key={movie.id}
id={movie.id}
coverImg={movie.medium_cover_image}
rating={movie.rating}
runtime={movie.runtime}
description_full={movie.description_full}
background_image_original={movie.background_image_original}
title={movie.title}
genres={movie.genres}
/>
)}
</div>
);
};
export default Detail;
참고
Home에서 불러온 API는 영화 리스트들이고,
Detail에서 불러온 API는 영화 각각의 세부 데이터를 불러온 것이다.
import { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import MovieGroup from '../components/MovieGroup';
import Load from '../components/Load';
const List_arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const Group = () => {
const { group, page } = useParams();
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?page=${page}&${group}&sort_by=rating`
)
).json();
setMovies(json.data.movies);
};
useEffect(() => {
getMovies();
setLoading(false);
return;
}, [group, page]); // group이나 page 바뀔때마다
return (
<div>
{loading ? (
<Load />
) : (
<div>
{movies.map((movie) => (
<MovieGroup
key={movie.id}
id={movie.id}
title={movie.title}
coverImg={movie.medium_cover_image}
rating={movie.rating}
runtime={movie.runtime}
summary={movie.summary}
year={movie.year}
/>
))}
</div>
)}
{loading ? null : (
<div>
<div>
{List_arr.map((lst) => (
<Link key={lst} to={`/page/${group}/${lst}`}>
{lst}
</Link>
))}
</div>
</div>
)}
</div>
);
};
export default Group;
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import MovieSearch from '../components/MovieSearch';
import Load from '../components/Load';
const Search = () => {
const { search } = useParams();
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const [movieArr, setMovieArr] = useState([]);
const getMovies = () => {
for (let i = 1; i <= 100; i++) {
setLoading(true);
setMovies([]);
fetch(`https://yts.mx/api/v2/list_movies.json?page=${i}&sort_by=rating`)
.then((res) => res.json())
.then((json) => setMovies(json.data.movies));
}
setLoading(false);
};
useEffect(() => {
setLoading(true);
setMovieArr([]);
getMovies();
return;
}, [search]);
useEffect(() => {
if (movies.length === 0) {
return <Load />;
} else {
setMovieArr(
[
movieArr,
...[
movies.filter(
(movie) =>
movie.summary.toLowerCase().indexOf(search.toLowerCase()) !==
-1 ||
movie.description_full
.toLowerCase()
.indexOf(search.toLowerCase()) !== -1 ||
movie.title.toLowerCase().indexOf(search.toLowerCase()) !== -1
),
],
]
.flat()
.map((movie, i, arr) => {
for (let j = i + 1; j < arr.length; j++) {
if (
movie.id === arr[j].id &&
arr[j] !== undefined &&
movie !== undefined
) {
console.log(i, j);
console.log(movie.id, arr[j].id);
arr.splice(j, 1);
j -= 1;
}
}
return movie;
})
.sort((a, b) => b['rating'] - a['rating'])
);
}
}, [movies]);
return (
<div>
{loading ? (
<Load />
) : (
<div>
{movieArr.map((movie) => (
<MovieSearch
key={movie.id}
id={movie.id}
title={movie.title}
coverImg={movie.medium_cover_image}
rating={movie.rating}
runtime={movie.runtime}
summary={movie.summary}
year={movie.year}
/>
))}
</div>
)}
</div>
);
};
export default Search;
이제 스타일링만 해주면 끝!