# 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)

  1. Settings
  2. Initial movie list API
  3. Search movie function
  4. Logo
  5. Background Image
  6. Deploy

image-20201223021954424

# 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

image-20201223022204178

//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

# Notes

  1. What is Hash router and how is it different? Nicholas says having a normal route can be difficult to set in github pages.
  2. Function component vs Class component

# Initial Movie list API

# #React inline if else #async

# Flow

  1. this.state is created at constructor
  2. this.getMovies() at componentDidMount
  3. 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
//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

# Notes

  1. Deconstructor's usage

    const { isLoading, movies } = this.state;

  2. 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

  1. this.state created at constructor
  2. handleChange and handleSubmit binded
  3. onChange assigned to input and onSubmit assinged to form
  4. input value = {this.state.value}
  5. onChange -> state value changes -> input value changes
  6. 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

# Notes

# #form in React

import logo from "./af.png";

<img className="logo mb-4" src={logo} alt="" width="72" height="72" />;
1
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

# 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

npm run deploy

Last Updated: 3/1/2021, 9:19:08 PM