-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React Cinema - Ralph #5
base: master
Are you sure you want to change the base?
Changes from all commits
2792f13
6a6db11
4be3665
01b1b79
05becbb
d7f4544
f2f47ad
5a4b372
fe92891
a151f78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="stylesheet" src="./style.css"> | ||
<title>Hello World</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script src="dist/bundle.js"></script> | ||
</body> | ||
</html> | ||
|
||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="stylesheet" href="./src/css/style.css"> | ||
<title>OMDb Movie search</title> | ||
</head> | ||
|
||
<body> | ||
<div id="root"></div> | ||
<script src="dist/bundle.js"></script> | ||
</body> | ||
|
||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,142 @@ | ||
import React from 'react'; | ||
|
||
import Header from './Header'; | ||
import Search from './Search'; | ||
import Movies from './Movies'; | ||
import Loading from './elements/Loading'; | ||
import BackToTop from './elements/BackToTop'; | ||
|
||
|
||
class App extends React.Component { | ||
constructor(){ | ||
constructor() { | ||
super(); | ||
this.state = { | ||
search: [], | ||
searchTerm: "", | ||
page: 2, | ||
watchList: localStorage.getItem("watchList") ? localStorage.getItem("watchList") : [], | ||
watchListSection: false | ||
} | ||
this.receiverSearch = this.receiverSearch.bind(this); | ||
this.receiverWatchList = this.receiverWatchList.bind(this); | ||
this.receiverDisplayWatchlist = this.receiverDisplayWatchlist.bind(this); | ||
this.handleScroll = this.handleScroll.bind(this); | ||
} | ||
|
||
handleScroll() { | ||
const windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight; | ||
const body = document.body; | ||
const html = document.documentElement; | ||
const docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); | ||
const windowBottom = windowHeight + window.pageYOffset; | ||
const backToTop = body.querySelector("#back-to-top"); | ||
|
||
const loading = document.querySelector('#search-results__loading'); | ||
const OMBbAPIKey = "aba7af8e"; | ||
const query = 'http://www.omdbapi.com/?s=' + this.state.searchTerm + '&apikey=' + OMBbAPIKey + '&page=' + this.state.page; | ||
|
||
// Back to top button | ||
if (body.scrollTop > 300 || html.scrollTop > 300) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than manipulating the DOM by adding classes, it would be better to set property in state based on scrollTop and set the class using JSX |
||
backToTop.classList.add('visible'); | ||
} else { | ||
backToTop.classList.remove('visible'); | ||
} | ||
|
||
// Infinite scroll | ||
if (windowBottom >= docHeight) { | ||
fetch(query) | ||
.then(response => { | ||
loading.textContent = "Loading..."; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As with previous comment, it's good practice to avoid manipulating DOM directly and let JSX handle it. Let me know if you want me to help with this |
||
loading.classList.add('show'); | ||
return response.json(); | ||
} | ||
) | ||
.then(movies => { | ||
if (movies.Search) { | ||
const self = this; | ||
setTimeout(function () { | ||
self.setState({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can avoid using |
||
search: [...self.state.search, ...movies.Search], | ||
page: self.state.page += 1 | ||
}); | ||
loading.classList.remove('show'); | ||
}, 500) | ||
} else { | ||
loading.textContent = "No more results"; | ||
loading.classList.add('show'); | ||
} | ||
}) | ||
.catch(error => { | ||
console.log(error); | ||
loading.textContent = "There's been a problem fetching the results. Try again later.."; | ||
loading.classList.add('show'); | ||
}); | ||
|
||
} | ||
} | ||
|
||
componentDidMount() { | ||
window.addEventListener("scroll", this.handleScroll); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice |
||
} | ||
|
||
receiverSearch(movies, searchTerm) { | ||
this.setState({ | ||
search: movies, | ||
searchTerm: searchTerm, | ||
watchListSection: false | ||
}) | ||
} | ||
|
||
receiverDisplayWatchlist() { | ||
this.setState({ | ||
watchListSection: true | ||
}) | ||
} | ||
|
||
receiverWatchList(articleTitle, action) { | ||
if (action === "addToWatchList") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest implementing this as two functions. One will do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keeping function to a single purpose will keep manageable and prevent them from becoming too large and doing too many things |
||
this.state.search.forEach(articleObj => { | ||
return Object.keys(articleObj).find(articleKey => { | ||
if (articleObj[articleKey] === articleTitle) { | ||
this.setState({ | ||
watchList: [...this.state.watchList, articleObj] | ||
}) | ||
|
||
localStorage.getItem("watchList") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we are using localStorage multiple times here, I would suggest using variables to store value from localStorage, update it depending on value found and write updated value back. It will avoid excess reads and make it easier to follow. For example const watchList = localStorage.getItem("watchList");
const updatedWatchList = watchList ? [articleObj, [...JSON.parse(watchList)] : [...this.state.watchList, articleObj];
localStorage.setItem("watchList", JSON.stringify(updatedWatchList)); |
||
? localStorage.setItem("watchList", JSON.stringify([articleObj, ...JSON.parse(localStorage.getItem("watchList"))])) | ||
: localStorage.setItem("watchList", JSON.stringify([...this.state.watchList, articleObj])); | ||
} | ||
}); | ||
}); | ||
} else { | ||
const index = JSON.parse(this.state.watchList).forEach((item, index) => { | ||
if (item.Title === articleTitle) { | ||
// console.log("Aquis", this.state.watchList); | ||
this.setState({ | ||
watchList: JSON.parse(this.state.watchList).splice(index, 1) | ||
}) | ||
localStorage.setItem("watchList", this.state.watchList); | ||
// console.log("Aquis", localStorage.getItem("watchList")); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
render(){ | ||
render() { | ||
const watchList = this.state.watchListSection; | ||
let MoviesList; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. avoid using capitals for things which are not classes or component names There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that in this case it will contain a component instance, but it will be an instance of the class rather than the class itself. For example const movie = new Movie(); |
||
if (watchList) { | ||
MoviesList = <Movies movies={localStorage.getItem("watchList") ? JSON.parse(localStorage.getItem("watchList")) : this.state.watchList} receiver={this.receiverWatchList} watchlistbutton={false} />; | ||
} else { | ||
MoviesList = <Movies movies={this.state.search} receiver={this.receiverWatchList} watchlistbutton={true} />; | ||
} | ||
return ( | ||
<div> | ||
React cinema app | ||
<div className="container"> | ||
<Header title="OMDb Movie search" receiver={this.receiverDisplayWatchlist} /> | ||
<Search receiver={this.receiverSearch} /> | ||
{MoviesList} | ||
<Loading /> | ||
<BackToTop /> | ||
</div> | ||
) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
class Header extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.handleWatchList = this.handleWatchList.bind(this); | ||
} | ||
handleWatchList() { | ||
document.querySelectorAll('.movie-info__moreinfo').forEach(item => item.classList.remove("disabled")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be better to handle this using JSX rather than DOM manipulation as a re-render could blow out your changes and create some strange bugs |
||
this.props.receiver(); | ||
} | ||
render() { | ||
return ( | ||
|
||
<header className="main-header"> | ||
<h1 className="main-heading">{this.props.title}</h1> | ||
<button onClick={this.handleWatchList} className="movie-info__moreinfo watch-list__button">Watch List</button> | ||
</header> | ||
) | ||
} | ||
} | ||
export default Header; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import React from 'react'; | ||
|
||
class Movie extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.movieFetch = this.movieFetch.bind(this); | ||
this.handleWatchList = this.handleWatchList.bind(this); | ||
}; | ||
movieFetch(e) { | ||
const evtTarget = e.target; | ||
const movieParentNode = evtTarget.parentNode; | ||
const OMBbAPIKey = "aba7af8e"; | ||
fetch('http://www.omdbapi.com/?t=' + this.props.title + '&apikey=' + OMBbAPIKey) | ||
.then(response => { | ||
return response.json(); | ||
}) | ||
.then(data => { | ||
// Show/hide the list | ||
if (movieParentNode.parentNode.querySelector(".movie-info__moreinfo-details")) { | ||
// Remove list | ||
movieParentNode.parentNode.querySelector(".movie-info__moreinfo-details").remove(); | ||
// Update button text | ||
evtTarget.textContent = "Info"; | ||
} else { | ||
// Create list with desired movie details | ||
const infoToDisplay = [ | ||
"Genre", | ||
"Plot", | ||
"Runtime", | ||
"Awards", | ||
"Language" | ||
]; | ||
const html = Object.keys(data) | ||
.map(function (key) { | ||
if (infoToDisplay.indexOf(key) !== -1) { | ||
return `<li><strong>${key}</strong>: ${data[key]}</li>`; | ||
} | ||
}) | ||
.join(""); | ||
|
||
// Update button text | ||
evtTarget.textContent = "Close"; | ||
|
||
// Append list | ||
const moreInfoList = document.createElement("ul"); | ||
moreInfoList.setAttribute("class", "movie-info__moreinfo-details"); | ||
moreInfoList.innerHTML = html; | ||
movieParentNode.parentNode.append(moreInfoList); | ||
} | ||
}) | ||
.catch(error => { | ||
console.error(error); | ||
}); | ||
} | ||
handleWatchList(e, action) { | ||
const evtTarget = e.target; | ||
if (!evtTarget.classList.contains('disabled')) { | ||
evtTarget.classList.add("disabled"); | ||
const articleTitle = evtTarget.getAttribute("data-title"); | ||
this.props.receiver(articleTitle, action); | ||
} | ||
} | ||
render() { | ||
const watchlistbutton = this.props.watchlistbutton; | ||
return ( | ||
<div id="movie-details" className="movie-details"> | ||
<div className='movie-poster'> | ||
<img src={this.props.poster === 'N/A' ? 'http://via.placeholder.com/150x220?text=Image' : this.props.poster} alt={this.props.title} className='movie-poster__image' /> | ||
</div> | ||
<div className='movie-info'> | ||
<h2 className='movie-info__title'>{this.props.title}</h2> | ||
<div className='movie-info__year'><strong>Year:</strong> { | ||
this.props.year | ||
}</div> | ||
<a className='movie-info__imdb-link' target='_blank' href={'https://www.imdb.com/title/' + this.props.imdbID} >IMDb ↗</a> | ||
<a id='' | ||
className='movie-info__imdb-link' | ||
target="_blank" href={'https://www.youtube.com/results?search_query=' + this.props.title + " " + this.props.year + " trailer"}> | ||
Watch trailer ↗ | ||
</a> | ||
{watchlistbutton | ||
? <button id='' onClick={(e) => this.handleWatchList(e, "addToWatchList")} className='movie-info__moreinfo' data-title={this.props.title}>Add to watch list</button> | ||
: <button id='' onClick={(e) => this.handleWatchList(e, "deleteFromWatchList")} className='movie-info__moreinfo' data-title={this.props.title}>Remove from watch list</button>} | ||
<button onClick={(e) => this.movieFetch(e)} id='movie-info__moreinfo' className='movie-info__moreinfo'>Info</button> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
} | ||
|
||
export default Movie; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import React from 'react'; | ||
import Movie from './Movie'; | ||
|
||
|
||
class Movies extends React.Component { | ||
constructor() { | ||
super(); | ||
this.watchListReceiver = this.watchListReceiver.bind(this); | ||
} | ||
|
||
watchListReceiver(title, action) { | ||
this.props.receiver(title, action); | ||
} | ||
|
||
render() { | ||
|
||
console.log(this.props.movies.length); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. avoid committing console.logs |
||
console.log(this.props.movies); | ||
return ( | ||
this.props.movies.length > 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid having multi-line ternaries in JSX. I would use an if/else and return from each block. |
||
? | ||
<section className="search-results" id="search-results"> | ||
<div className='movie-list-wrapper'> | ||
{this.props.movies.map((movie) => { | ||
return <Movie | ||
watchlistbutton={this.props.watchlistbutton} | ||
key={movie.imdbID} | ||
poster={movie.Poster} | ||
title={movie.Title} | ||
year={movie.Year} | ||
imdbID={movie.imdbID} | ||
receiver={this.watchListReceiver} />; | ||
})} | ||
</div> | ||
</section> | ||
: | ||
"" | ||
); | ||
} | ||
} | ||
|
||
export default Movies; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may need to perform JSON.parse on item retrieved from localStorage otherwise I expect it will just be a string