Skip to content
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

My React Cinema #16

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 13 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,21 @@
# React Cinema

Let's revisit our first project where we built a movie search engine using the Open Movie Database. This time we want to implement it using React. It should be a Single Page App, that is all the functionality should be on a single page, rather switch between multiple pages.
This was the weekend project for Week 4 of the 12 week boot camp I have been doing at Constructor Labs. Earlier in the week we had our first introduction to using React and this was our chance to put our new found powers to good use by recreating the project from the previous week which was done using vanilla js.

Before starting to code produce a diagram using pen and paper of your application which shows both layout and a tree diagram of the components.
The goal of the project was to implement a movie search database (with favourites) using the omdb api and React.

What are some of the components you are going to need? Which components will fetch data and how will that data be displayed? Which components will store state and how will they pass data to other components? Which components should be re-used? Rather than re-implementing your previous solution again have a think about what you have learned in the past week and how you can apply it here.
The core features we were asked to add were:

You can start coding once your plan has been checked by a TA or instructor.
* Movie search
* More details when a film was selected
* Pagination where needed for search results
* Favourites (and local storage)

## The brief
At this point in the course we're still on purely browser based technology so this is all running on the front-end.

We want to create a movie search engine. To power it we will use the [Open Movie Database](http://www.omdbapi.com) API.
A few things I wish I'd done better with this project:
* Some of the naming isn't as obvious as it could be. Another 30 mins of planning upfront could have been useful.
* There was probably more logic than neccessary in some of the componenents that could have been kept in the main App component.
* Infinite scrolling would have been nice but nothind I did seemed to make it work.

To start using the OMDB API you will first need to sign up with them to receive and API key. The key issued to you will allow you 1000 requests per day and you will need to include this key as part of every request.

To get started, fork and clone this repo. Please submit a pull request after your first commit and push commits regularly.

You should complete as many of the following tasks as you can.

- [ ] Work using mobile first, that is create the mobile version first and add tablet and desktop versions after.
- [ ] Create an HTML page which should have a `form` at the top which contains a text input and a submit button. Below it should have a placeholder element for the returned results.
- [ ] Use JavaScript to capture the `submit` event in your search form, extract the query string from the text input and use that to make an API call to the Open Movie Database API to search for films which match the query string using `fetch`. `console.log` the results
- [ ] Display the data returned by the API including title, year and poster picture

**Movie details**

- [ ] Adjust your layout to create room for a detailed view of movie information
- [ ] Capture clicks on your movie results items and use that information to make another request to the API for detailed movie information. Using event delegation will help you here. `console.log` the returned result
- [ ] Display the detailed movie result in the in the details view you created earlier
- [ ] Make your design responsive and ensure it looks great at different screen widths

**Your own feature**

- [ ] Implement any feature you would find useful or interesting

**Stretch goals**

- [ ] Implement pagination so that users can navigate between all movies in search results rather than just the first ten
- [ ] Create a favourites list. It's up to you how you would add items to favourites. You could add a button or otherwise. Display a list of favourites somewhere on your page.
- [ ] Make the favourites list sortable. Add `up` and `down` buttons to your favourites which on click will move the result in relevant direction
- [ ] Save favourites locally using `localStorage` so that favourites persist in browser after refresh
- [ ] Let's create a search preview. It should listen for input events and submit a search request with current query string. Display the search preview results in an absolute positioned container just below the search box.
Hint: You may want to kick of the searching after at least 3 characters have been typed.

## Objectives

* We want to see great looking webpages that work well at all screen widths
* Your code should have consistent indentation and sensible naming
* Use lots of concise, reusable functions with a clear purpose
* Add code comments where it is not immediately obvious what your code does
* Your code should not throw errors and handle edge cases gracefully. For example not break if server fails to return expected results
* Use BEM methodology to style your page
* Try to use pure functions as much as possible, but keep in mind it will not be possible to make all functions pure.

## README.md

When finished, include a README.md in your repo. Someone who is not familiar with the project should be able to look at it and understand what it is and what to do with it. Explain functionality created, mention any outstanding issues and possible features you would include if you had more time. List technologies used to create the app. Include a screenshot of your app in the README.
Overall I enjoyed building this project and was am pretty happy with how it turned out.
6 changes: 5 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400i|Roboto:500" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<title>Hello World</title>
<title>PhlX</title>
</head>
<body>
<div id="root"></div>
Expand Down
155 changes: 153 additions & 2 deletions src/components/App.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,165 @@
import React from 'react';
import Search from './Search'
import Results from './Results'

class App extends React.Component {
constructor(){
super();

this.state = {
query : '',
results : [],
selectedMovie : '',
page: 1,
resultsLeft:0,
favouritesArray : [],
favouritesLength: 0,
favouritesObject: {test:123}
}

this.receiveQuery = this.receiveQuery.bind(this);
this.receiveFavourite = this.receiveFavourite.bind(this);
this.receiveMovie = this.receiveMovie.bind(this);
this.receiveMoreMovies = this.receiveMoreMovies.bind(this);
this.createUrl = this.createUrl.bind(this);
this.fetchResults = this.fetchResults.bind(this);
this.showFavourites = this.showFavourites.bind(this)
this.updateFavouritesArray = this.updateFavouritesArray.bind(this)
}

//****************************************//
// Setting up local storage - putting any existing local storage into state

componentWillMount() {
let favObject = JSON.parse(localStorage.getItem('reactFavourites'))

if (favObject) {
this.setState({
favouritesObject : favObject
})
}
}

//****************************************//
// creates an array of favourites. This is necessary to show the amount (.length) of favourites on top bar.

componentDidMount() {
this.updateFavouritesArray();
}


//****************************************//
// Fetching data from omdb API

// builds the url for omdb api request
createUrl(typeOfSearch, search) {
const baseURL = "http://www.omdbapi.com/";
const apiKey = "95869d44";
const page = this.state.page;
return `${baseURL}?apikey=${apiKey}&${typeOfSearch}=${search}&page=${page}&type=movie`
}

fetchResults() {
fetch(this.createUrl('s', this.state.query)).
then(response => response.json()).
then(body => {
this.state.page===1?
(this.setState({results: body.Search})):
(this.setState({results: this.state.results.concat(body.Search)}))
this.setState({resultsLeft: body.totalResults-(this.state.page*10)});

})
}


//****************************************//
// Receive search query from Search componenent


receiveQuery(query) {
this.setState({
query: query,
page: 1
},this.fetchResults)
}

//****************************************//
// Receive selected movie from Result component

receiveMovie(selectedMovie) {
this.setState({
selectedMovie:selectedMovie
})
}

//****************************************//
// Request to add another page of results (sent from Results component)

receiveMoreMovies() {
this.setState({
page:this.state.page+1
},this.fetchResults)

}

//****************************************//
// Add a movie to favourites object (App state)

receiveFavourite(result) {
const favObject = this.state.favouritesObject
if (favObject.hasOwnProperty(result.imdbID)) {
delete favObject[result.imdbID]
} else {
favObject[result.imdbID] = result
}
this.setState({
favouritesObject:favObject
},localStorage.setItem('reactFavourites', JSON.stringify(this.state.favouritesObject)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the localStorage call from callback, by passing it favObject rather than this.state.favouritesObject


this.updateFavouritesArray();
}

//****************************************//
// Create an array of favourites from favourites object. This is used to update the App/result state which will then show favourites instead of search

updateFavouritesArray() {
console.log(this.state.favouritesObject)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like favouritesArray is only being used to favouritesLength. I'd say neither proeprty is needed in state as both can be produced from favouritesObject

this.state.favouritesObject.hasOwnProperty('test')?delete this.state.favouritesObject.test:null
const favouritesKeys = Object.keys(this.state.favouritesObject);
this.state.favouritesArray = favouritesKeys.map(key => this.state.favouritesObject[key])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't assign to state directly, use setState

console.log(this.state.favouritesArray);
this.setState({
favouritesLength:this.state.favouritesArray.length
})
}

//****************************************//
// By setting the state of result to the favourites array favourites will now be shown instead of search results

showFavourites() {
this.setState({
results:this.state.favouritesArray,
resultsLeft:0
})
}

render(){
return (
<div>
React cinema app
<div className="app">
<div className="top">
<p className="top__favourites" onClick={this.showFavourites}>Favourites ({this.state.favouritesLength})</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than storing favouritesLength in state, it would be better to use Object.values(this.state.favouritesObject).length. That will ensure that you always working from the same source of truth rather than having to recalculate favouritesLength.

<div className="search">
<div className="search__inner-div">
<Search receiveQuery={this.receiveQuery}/>
<h1>PHLX</h1>
</div>
</div>
</div>

<div className="container">

<Results selectedMovie={this.state.selectedMovie} receiveMovie={this.receiveMovie} receiveMoreMovies={this.receiveMoreMovies} results={this.state.results} resultsLeft={this.state.resultsLeft} receiveFavourite={this.receiveFavourite} favObject={this.state.favouritesObject}/>
</div>
</div>
)
}
Expand Down
39 changes: 39 additions & 0 deletions src/components/Movie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
class Movie extends React.Component {

constructor() {
super()
this.state = {
movie:''
}

this.fetchResults = this.fetchResults.bind(this);
}

fetchResults(imdbID) {
fetch(`http://www.omdbapi.com/?apikey=95869d44&i=${imdbID}&page=1&type=movie`).
then(response => response.json()).
then(body => {
this.setState({
movie: body
})
console.log(body);
})
}

componentDidMount() {
this.fetchResults(this.props.imdbID);
}

render() {
return (
<ul className="more-info">
<li className="more-info__item"><strong>Plot: </strong> "{this.state.movie.Plot}"</li>
<li className="more-info__item"><strong>Starring: </strong>{this.state.movie.Actors}</li>
</ul>
)
}

}

export default Movie
37 changes: 37 additions & 0 deletions src/components/Result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import Movie from './Movie';

class Result extends React.Component{

constructor() {
super()
this.handleClick = this.handleClick.bind(this);
this.handleClickFavourite = this.handleClickFavourite.bind(this);
// this.handleClickFavourite = this.handleClickFavourite.bind(this);
}

handleClick () {
this.props.receiveMovie(this.props.showInfo ? null : this.props.result.imdbID)
}

handleClickFavourite() {
this.props.receiveFavourite(this.props.result)
}


render() {
console.log(this.props.isFavourite?'movies__fav-icon fas fa-star fa-2x':'movies__fav-icon far fa-star fa-2x')
return (
<div className="result">
<img className="result__image" src={this.props.result.Poster} id={this.props.result.imdbID} onClick={this.handleClick} />
<p className="result__tab">
<strong className="result__title-year">{this.props.result.Title} ({this.props.result.Year})</strong>
<i className={this.props.isFavourite?'result__fav-star fas fa-star fa-2x':'result__fav-star movies__fav-icon far fa-star fa-2x'} onClick={this.handleClickFavourite}></i>
</p>
{this.props.showInfo ? <Movie movie={this.props.result} imdbID={this.props.result.imdbID} /> : null}
</div>
)
}
}

export default Result;
Loading