
⇒ App.js
import { useState, useEffect } from "react";
function App() {
// toDo, toDos나 직접적으로 수정 불가능 >>> 함수를 가져와서 수정하게 만들어야함(setToDo, setToDos)
const [toDo, setToDo] = useState("");
const [toDos, setToDos] = useState([]);
const onChange = (event) => setToDo(event.target.value);
const onSubmit = (event) => {
event.preventDefault(); // 새로 고침 방지
if (toDo === "") {
return;
}
setToDos((currentArray) => [toDo, ...currentArray]);
setToDo("");
};
console.log(toDos);
return (
<div>
<h1>My To Dos ({toDos.length})</h1>
{/* form은 submit 이벤트를 갖고 있음 */}
<form onSubmit={onSubmit}>
<input
onChange={onChange}
value={toDo}
type="text"
placeholder="Write your to do..."
/>
<button>Add To Do</button>
</form>
</div>
);
}
export default App;

import { useState, useEffect } from "react";
function App() {
// toDo, toDos나 직접적으로 수정 불가능 >>> 함수를 가져와서 수정하게 만들어야함(setToDo, setToDos)
const [toDo, setToDo] = useState("");
const [toDos, setToDos] = useState([]);
const onChange = (event) => setToDo(event.target.value);
const onSubmit = (event) => {
event.preventDefault(); // 새로 고침 방지
if (toDo === "") {
return;
}
setToDos((currentArray) => [toDo, ...currentArray]);
setToDo("");
};
console.log(toDos);
return (
<div>
<h1>My To Dos ({toDos.length})</h1>
{/* form은 submit 이벤트를 갖고 있음 */}
<form onSubmit={onSubmit}>
<input
onChange={onChange}
value={toDo}
type="text"
placeholder="Write your to do..."
/>
<button>Add To Do</button>
</form>
<hr />
<ul>
{/* map((item, index) => {item}) >>> argument = 값 // index = 숫자 */}
{toDos.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default App;

⇒ App.js
import { useEffect, useState } from "react";
function App() {
const [loading, setLoading] = useState(true);
const [coins, setCoins] = useState([]);
useEffect(() => {
fetch("https://api.coinpaprika.com/v1/tickers")
.then((response) => response.json())
.then((json) => {
setCoins(json);
setLoading(false);
});
}, []);
return (
<div>
<h1>The Coins! {loading ? "" : `(${coins.length})`}</h1>
{loading ? (
<strong>Loading...</strong>
) : (
<select>
{coins.map((coin) => (
<option>
{coin.name} ({coin.symbol}: ${coin.quotes.USD.price})
</option>
))}
</select>
)}
</div>
);
}
export default App;

⇒ App.js
import { useEffect, useState } from "react";
function App() {
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?minimum_rating=8.8&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
};
useEffect(() => {
getMovies();
}, []);
return (
<div>
{loading ? (
<h1>Loading...</h1>
) : (
<div>
{movies.map((movie) => (
<div key={movie.id}>
<img src={movie.medium_cover_image} />
<h2>{movie.title}</h2>
<p>{movie.summary}</p>
<ul>
{movie.genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
))}
</div>
)}
</div>
);
}
export default App;

⇒ src/App.js
function App() {
return null;
}
export default App;
⇒ src/routes.Home.js
import { useEffect, useState } from "react";
import Movie from "../components/Movie";
function Home() {
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?minimum_rating=8.8&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}
coverImg={movie.medium_cover_image}
title={movie.title}
summary={movie.summary}
genres={movie.genres}
/>
))}
</div>
)}
</div>
);
}
export default Home;
⇒ src/routes.Detail.js
function Detail() {
return <h1>Detail</h1>;
}
export default Detail;
⇒ src/components/Movie.js
import PropTypes from "prop-types";
function Movie({ coverImg, title, summary, genres }) {
return (
<div>
<img src={coverImg} alt={title} />
<h2>{title}</h2>
<p>{summary}</p>
<ul>
{genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
);
}
Movie.propTypes = {
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;
⇒ src/App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Detail from "./routes/Detail";
import Home from "./routes/Home";
function App() {
return (
<Router>
<Switch>
<Route path="/hello">
<h1>Hello</h1>
</Route>
<Route path="/movie">
<Detail />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
export default App;
src/routes/Detail.js 의 내용이 나옴
// src/routes/Detail.js
function Detail() {
return <h1>Detail</h1>;
}
export default Detail;
⇒ src/components/Movie.js
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
function Movie({ coverImg, title, summary, genres }) {
return (
<div>
<img src={coverImg} alt={title} />
<h2>
<Link to="/movie">{title}</Link>
</h2>
<p>{summary}</p>
<ul>
{genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
);
}
Movie.propTypes = {
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;
⇒ http://localhost:3000/movie 을 치거나 각 영화의 제목을 클릭한 경우

⇒ http://localhost:3000/movie 을 치거나 각 영화의 제목을 클릭한 경우

⇒ src/App.js
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Detail from "./routes/Detail";
import Home from "./routes/Home";
function App() {
return (
<Router>
<Switch>
<Route path="/abot-us">
<h1>Hello</h1>
</Route>
<Route path="/movie/:id">
<Detail />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
export default App;
⇒ src/components/Movie.js
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
function Movie({ id, coverImg, title, summary, genres }) {
return (
<div>
<img src={coverImg} alt={title} />
<h2>
<Link to={`/movie/${id}`}>{title}</Link>
</h2>
<p>{summary}</p>
<ul>
{genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
);
}
Movie.propTypes = {
id: PropTypes.number.isRequired,
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;
⇒ src/routes/Detail.js
import { useEffect } from "react";
import { useParams } from "react-router-dom";
function Detail() {
const { id } = useParams();
const getMovie = async () => {
const json = await (
await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
).json();
console.log(json);
};
useEffect(() => {
getMovie();
}, []);
return <h1>Detail</h1>;
}
export default Detail;
⇒ src/routes/Home.js
import { useEffect, useState } from "react";
import Movie from "../components/Movie";
function Home() {
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?minimum_rating=8.8&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}
id={movie.id}
coverImg={movie.medium_cover_image}
title={movie.title}
summary={movie.summary}
genres={movie.genres}
/>
))}
</div>
)}
</div>
);
}
export default Home;
⇒ src/components/MovieInfo.js
function MovieInfo({ description, downCount, genre, coverImg, title }) {
return (
<div>
<img src={coverImg} alt={title} />
<h2>{title}</h2>
<ul>
{genre.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
<p>DownLoaded Count: {downCount}</p>
<p>{description}</p>
</div>
);
}
export default MovieInfo;
⇒ src/routes/Detail.js
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import MovieInfo from "../components/MovieInfo";
function Detail() {
const [loading, setLoading] = useState(true);
const [movie, setMovie] = useState([]);
const { id } = useParams();
console.log(id);
const getMovie = async () => {
const json = await (
await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
).json();
setMovie(json.data.movie);
setLoading(false);
};
useEffect(() => {
getMovie();
}, []);
return (
<div>
{loading ? (
<h1>Loading...</h1>
) : (
<MovieInfo
description={movie.description_full}
downCount={movie.download_count}
genre={movie.genres}
coverImg={movie.medium_cover_image}
title={movie.title_long}
/>
)}
</div>
);
}
export default Detail;
⇒ src/routes/Detail.js
import { useState, useEffect, useCallback } from "react";
import { useParams } from "react-router-dom";
import MovieInfo from "../components/MovieInfo";
function Detail() {
const [loading, setLoading] = useState(true);
const [movie, setMovie] = useState([]);
const { id } = useParams();
const getMovie = useCallback(async () => {
// Use useCallback to memoize getMovie
const json = await (
await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)
).json();
setMovie(json.data.movie);
setLoading(false);
}, [id]); // Depend on id, although it's stable for the component lifecycle
useEffect(() => {
getMovie();
}, [getMovie]); // Include getMovie in the dependency array
return (
<div>
{loading ? (
<h1>Loading...</h1>
) : (
<MovieInfo
description={movie.description_full}
downCount={movie.download_count}
genre={movie.genres}
coverImg={movie.medium_cover_image}
title={movie.title_long}
/>
)}
</div>
);
}
export default Detail;
⇒ src/style.css
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
background-color: #eff3f7;
height: 100%;
}
⇒ src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import "./styles.css";
ReactDOM.render(<App />, document.getElementById("root"));
⇒ src/components/Movie.module.css
.movie {
background-color: white;
margin-bottom: 70px;
font-weight: 300;
padding: 20px;
border-radius: 5px;
color: #adaeb9;
display: grid;
grid-template-columns: minmax(150px, 1fr) 2fr;
grid-gap: 20px;
text-decoration: none;
color: inherit;
box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25),
0 8px 16px -8px rgba(0, 0, 0, 0.3), 0 -6px 16px -6px rgba(0, 0, 0, 0.025);
}
.movie__img {
position: relative;
top: -50px;
max-width: 150px;
width: 100%;
margin-right: 30px;
box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25),
0 18px 36px -18px rgba(0, 0, 0, 0.3), 0 -12px 36px -8px rgba(0, 0, 0, 0.025);
}
.movie__title,
.movie__year {
margin: 0;
font-weight: 300;
text-decoration: none;
}
.movie__title a {
margin-bottom: 5px;
font-size: 24px;
color: #2c2c2c;
text-decoration: none;
}
.movie__genres {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
margin: 5px 0px;
}
.movie__genres li,
.movie__year {
margin-right: 10px;
font-size: 14px;
}
⇒ src/components/Movie.js
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import styles from "./Movie.module.css";
function Movie({ id, coverImg, title, year, summary, genres }) {
return (
<div className={styles.movie}>
<img src={coverImg} alt={title} className={styles.movie__img} />
<div>
<h2 className={styles.movie__title}>
<Link to={`/movie/${id}`}>{title}</Link>
</h2>
<h3 className={styles.movie__year}>{year}</h3>
<p>{summary.length > 235 ? `${summary.slice(0, 235)}...` : summary}</p>
<ul className={styles.movie__genres}>
{genres.map((g) => (
<li key={g}>{g}</li>
))}
</ul>
</div>
</div>
);
}
Movie.propTypes = {
id: PropTypes.number.isRequired,
coverImg: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;
⇒ src/routes/Home.module.css
.container {
height: 100%;
display: flex;
justify-content: center;
}
.loader {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-weight: 300;
}
.movies {
display: grid;
grid-template-columns: repeat(2, minmax(400px, 1fr));
grid-gap: 100px;
padding: 50px;
width: 80%;
padding-top: 70px;
}
@media screen and (max-width: 1090px) {
.movies {
grid-template-columns: 1fr;
width: 100%;
}
}
⇒ src/routes/Home.js
import { useEffect, useState } from "react";
import Movie from "../components/Movie";
import styles from "./Home.module.css";
function Home() {
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?minimum_rating=8.8&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
};
useEffect(() => {
getMovies();
}, []);
return (
<div className={styles.container}>
{loading ? (
<div className={styles.loader}>
<span>Loading...</span>
</div>
) : (
<div className={styles.movies}>
{movies.map((movie) => (
<Movie
key={movie.id}
id={movie.id}
year={movie.year}
coverImg={movie.medium_cover_image}
title={movie.title}
summary={movie.summary}
genres={movie.genres}
/>
))}
</div>
)}
</div>
);
}
export default Home;
⇒ 메인 홈

⇒ 특정 영화 선택
