# Movie torrent search with React
SPA to search for movie torrents. Made with yts API and React. Also my first project with React. List movies on main page. Responsive card grid with poster, title, rating, year and torrent url button.
Check Final Product Here! (opens new window)
- Settings
- Initial movie list API
- Search movie function
- Logo
- Background Image
- Deploy
# 1. Settings
# Flow
npm install npx -g
install npx on global
No longer needed after newer node
npx create-react-app movie_app
npm start
//App.js
import React from "react";
import { HashRouter, Route } from "react-router-dom";
import About from "./routes/About";
import Detail from "./routes/Detail";
import Home from "./routes/Home";
import Navigation from "./components/Navigation";
import "./App.css";
function App() {
return (
<HashRouter>
<Navigation />
<Route path="/about" component={About} />
<Route path="/" exact={true} component={Home} />
<Route path="/movie-detail" component={Detail} />
</HashRouter>
);
}
export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Notes
- What is Hash router and how is it different? Nicholas says having a normal route can be difficult to set in github pages.
- Function component vs Class component
# Initial Movie list API
# #React inline if else #async
# Flow
- this.state is created at constructor
- this.getMovies() at componentDidMount
- getMovies() asynchronously requests API and setState
import React from "react";
import axios from "axios";
import Movie from "../components/Movie";
import "./Home.css";
import logo from "./af.png";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
movies: [],
value: "",
};
// this.handleChange = this.handleChange.bind(this);
// this.handleSubmit = this.searchMovie.bind(this);
}
// searchMovie = async (event) => {
// event.preventDefault();
// console.log(this.state.value);
// try {
// const movies = await axios.get(
// `https://yts.mx/api/v2/list_movies.json?query_term=${this.state.value}`
// );
// console.log(movies.data.data.movies);
// this.setState({
// movies: movies.data.data.movies.slice(0, 12),
// isLoading: false,
// });
// } catch (err) {
// alert("Oops couldn't find a match! Sorry :p");
// console.log(err);
// }
// };
getMovies = async () => {
try {
const movies = await axios.get(
"https://yts.mx/api/v2/list_movies.json?sort_by=rating?with_rt_ratings=true?xt=urn:btih:TORRENT_HASH&dn=Url+Encoded+Movie+Name&tr=http://track.one:1234/announce&tr=udp://track.two:80"
);
console.log(movies.data.data.movies);
this.setState({
movies: movies.data.data.movies.slice(0, 12),
isLoading: false,
});
} catch (err) {
console.log(err);
}
};
// handleChange(event) {
// this.setState({ value: event.target.value });
// }
componentDidMount() {
this.getMovies();
}
render() {
// return <div> {this.state.isLoading ? "Loading" : "Ready" </div>
// Modern JS
const { isLoading, movies } = this.state;
return (
<section className="d-flex flex-column align-items-center">
<img className="logo mb-4" src={logo} alt="" width="72" height="72" />
<form
className="form-group"
onSubmit={this.searchMovie}
autoComplete="off"
>
<input
value={this.state.value}
className="form-control"
type="text"
name="search"
id="search"
placeholder="What movie do you like?"
onChange={this.handleChange}
/>
</form>
{isLoading ? (
<div className="container loader">
<span className="loader__text">Just a second please :)</span>
</div>
) : (
<div className="container d-flex flex-wrap justify-content-center">
{movies.map((movie) => (
<Movie
key={movie.id}
id={movie.id}
year={movie.year}
title={movie.title}
rating={movie.rating}
poster={movie.medium_cover_image}
genres={movie.genres}
torrents={movie.torrents}
/>
))}
</div>
)}
</section>
);
}
}
export default Home;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//Movie.js
import React from "react";
import PropTypes from "prop-types";
import "./Movie.css";
function Movie({ id, year, title, rating, poster, torrents }) {
return (
<div className="card">
<img className="card-img-top" src={poster} alt={title} title={title} />
<div className="card-body">
<h5 className="movie__title">{title}</h5>
<div className="d-flex justify-content-between">
<h5>{year}</h5>
<h5>IMDB {rating}</h5>
</div>
</div>
<div className="card-body d-flex align-items-end">
{torrents.map((torrent, index) => (
<button
type="button"
onClick={(e) => {
e.preventDefault();
window.location.href = `${torrent.url}`;
}}
key={index}
className="torrents__torrent__btn btn btn-link"
>
{torrent.quality} Download
</button>
))}
</div>
</div>
);
}
Movie.propTypes = {
id: PropTypes.number.isRequired,
year: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
rating: PropTypes.number.isRequired,
poster: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Movie;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Notes
# #React Button Link #Deconstructor
Deconstructor's usage
const { isLoading, movies } = this.state;
Button
<button type="button" onClick={(e) => { e.preventDefault(); window.location.href = `${torrent.url}`; }} key={index} className="torrents__torrent__btn btn btn-link" > {torrent.quality} Download </button>
# 3. Search Movie
# Flow
- this.state created at constructor
- handleChange and handleSubmit binded
- onChange assigned to input and onSubmit assinged to form
- input value = {this.state.value}
- onChange -> state value changes -> input value changes
- onSubmit -> search movie
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
movies: [],
value: "",
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.searchMovie.bind(this);
}
searchMovie = async (event) => {
event.preventDefault();
console.log(this.state.value);
try {
const movies = await axios.get(
`https://yts.mx/api/v2/list_movies.json?query_term=${this.state.value}`
);
console.log(movies.data.data.movies);
this.setState({
movies: movies.data.data.movies.slice(0, 12),
isLoading: false,
});
} catch (err) {
alert("Oops couldn't find a match! Sorry :p");
console.log(err);
}
};
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
return(
<form
className="form-group"
onSubmit={this.searchMovie}
autoComplete="off"
>
<input
value={this.state.value}
className="form-control"
type="text"
name="search"
id="search"
placeholder="What movie do you like?"
onChange={this.handleChange}
/>
</form>
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Notes
# #form in React
# 4. Logo
import logo from "./af.png";
<img className="logo mb-4" src={logo} alt="" width="72" height="72" />;
1
2
3
2
3
Images must be imported to be used in React. This is because of webpack.
Alternative is to use public folder. See Wiki/react
for more.
# 5. Background Image
// css
body {
background-image: url("theater2.jfif");
}
//works. but requires jpg file at same location as CSS>
1
2
3
4
5
2
3
4
5
# 6. Deploy
npm i gh-pages
// package.json
.
.
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
// Add homepage part
"homepage": "https://keithkwon.dev/movie_app_react/"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
npm run deploy