For today I was thinking that we could build an App using React, and because the people from the Chingu group I’m in are working to create a Book Finder App I decided to build something similar. I also wanted to avoid building the exact same app as I would spoil them by writing this article. :innocent:
So… We’re going to build a Movie Search App! :smiley:
Below you can see the Movie Card design we’re going to use in our app:
The design was inspired from this Dribbble by Rizka Prayuda.
Note: I won’t go into details of how React works in this article (although we’ll only use basic concepts). If you read further I’ll assume that you’ve worked with React before. At least a little bit. :wink:
The Movie Card Component
Before building the Movie Card Component, we need to talk about the API we are going to use, because this will basically dictate how the HTML structure will look like.
For this example I used the OMDb API as it is very easy to get an apiKey and they give you access to a lot of informations about movies.
So… after I studied their API a little bit, I learned that we can send them an IMDb movie ID as a query parameter and they’ll return a JSON with all the data we need. In our example we’re going to extract the following properties: Title, Plot, Poster, Released date, a comma separated list of all the Genres and the imdbRating.
Bellow you’ll see how the MovieCard
Component will look like:
class MovieCard extends React.Component {
state = {
movieData: {}
};
render() {
const {
Title,
Released,
Genre,
Plot,
Poster,
imdbRating
} = this.state.movieData;
if (!Poster || Poster === 'N/A') {
return null;
}
return (
<div className="movie-card-container">
<div className="image-container">
<div
className="bg-image"
style={{ backgroundImage: `url(${Poster})` }}
/>
</div>
<div className="movie-info">
<h2>Movie Details</h2>
<div>
<h1>{Title}</h1>
<small>Released Date: {Released}</small>
</div>
<h4>Rating: {imdbRating} / 10</h4>
<p>{Plot && Plot.substr(0, 350)}</p>
<div className="tags-container">
{Genre &&
Genre.split(', ').map(g => (
<span key={g}>{g}</span>
))}
</div>
</div>
</div>
);
}
}
There are a few things I’d want to explain before we move forward:
- We’re setting
state.movieData
to be an empty object by default in order to avoid getting any errors as we are trying to access its properties in therender
method. Without doing so,movieData
would beundefined
and it will trigger an error. - If Poster doesn’t have a value or if it’s value is
N/A
wereturn null
. This will ensure that in this case theMovieCard
will not display anything. We wouldn’t want to see an empty card, do we? :stuck_out_tongue: - We set the Poster as a
backgroundImage
of a div. This is because we’ll useclip-path
in the CSS to give the image that rounded look. (Note that Clip-path is not working properly in all the browsers, but if you use Chrome, you’re fine :wink: ) - Instead of showing the entire Plot, I decided to only allow a maximum of 350 characters. That is why I used
.substring(0, 350)
on it. - As I said above, the Genre is a string which contains a comma separated list of all the genres. We
split()
the Genre string using a comma and a space:', '
. This will return an array that can bemapped
and converted into an array of individual<span>
tags containing the corresponding genre.
Also, you might notice that we use the short-circuit method: Plot && Plot.substr(0, 350)
. This will make sure that we only call the .substr()
method on the Plot
if Plot
is not undefined
. Read more about this technique on MDN.
Calling the API
So far, so good… We have the Component and now we need to make an API call to the OMBd endpoint and retrieve the movie data we need. For this I’m going to use Axios as it provides an easy way to do what we want.
We’ll add the axios
call in the MovieCard
’s componentDidMount lifecycle.
class MovieCard extends React.Component {
state = {
movieData: {}
};
componentDidMount() {
axios
.get(
`https://www.omdbapi.com/?apikey=${your_API}&i=${
this.props.movieID
}&plot=full`
)
.then(res => res.data)
.then(res => {
this.setState({ movieData: res });
});
}
render() {
// ... the rest of the code
}
}
Axios.get()
will return a promise containing the response. After that, by calling the setState
method we save the response into movieData
.
Note:
- To successfully call the API endpoint you’ll need an apikey. You can get one from the OMDb website.
- Besides the apiKey we also pass the IMDB movie ID as the
i=
query parameter. This value is coming fromthis.props
meaning that when we’ll use the<MovieCard>
we’ll have to pass themovieID
as a prop. You’ll see what I mean in the next section. :blush:
The MovieList Component
This component will do the following:
- Provide a
form
with aninput
that will be used by the user to submit a search term. - Using the search term, it’ll make a request to the OMDb endpoint in order to receive a list of all the movies which contain the corresponding term in their titles.
- Display the results in a list of
MovieCard
’s.
class MoviesList extends React.Component {
state = {
// By default I added a movie so when you first load the application it will show it -> FROZEN :smiley:
moviesList: ['tt2294629'],
searchTerm: ''
};
search = event => {
event.preventDefault();
axios
.get(
`https://www.omdbapi.com/?apikey=${your_API}&s=${
this.state.searchTerm
}&plot=full`
)
.then(res => res.data)
.then(res => {
if (!res.Search) {
this.setState({ moviesList: [] });
return;
}
const moviesList = res.Search.map(movie => movie.imdbID);
this.setState({
moviesList
});
});
};
handleChange = event => {
this.setState({
searchTerm: event.target.value
});
};
render() {
const { moviesList } = this.state;
return (
<div>
<form onSubmit={this.search}>
<input
placeholder="Search for a movie"
onChange={this.handleChange}
/>
<button type="submit">
<i className="fa fa-search" />
</button>
</form>
{moviesList.length > 0 ? (
moviesList.map(movie => (
<MovieCard movieID={movie} key={movie} />
))
) : (
<p>
Couldn't find any movie. Please search again using
another search criteria.
</p>
)}
</div>
);
}
}
We have a bigger code snippet above. Let’s break it down a little bit (at least the “important” stuff :smile:):
There are two event listeners.
- the
onChange
on theinput
- this will call thehandleChange
method which will update thesearchTerm
state with the input value every time the input is changed. - the
onSubmit
on theform
- this will call thesearch
method which will make a request to the API endpoint providing thesearchTerm
as a query parameter. It will save the response data into themoviesList
array (Note that we only save theimdbID
values from each returned object because this is all we need to pass to ourMovieCard
component as a prop).
Nevertheless, we’re checking to see if there is any data in res.Search
, otherwise we empty out the moviesList
array.
In the render
method, we check to see if the array has at least one item. If it doesn’t, we display an “error” message to the user.
The CSS
This time I won’t go over all the “mumbo-jumbo” we have in the CSS as I don’t want to make the post too long. But feel free to check out the CSS code on Github or on Codepen.
Briefly… I used flexbox
a bunch of times to put the items where I wanted to. I also used clip-path
to make that rounded form for the image, and at the end I added a little bit of media
queries to make the MovieCard
look better on mobile.
Other than that, all the CSS code is pretty clear. BUT… if you want me to go over it and explain it more in depth, let me know and I’ll update the post! :wink:
Conclusion
Even though the React part wasn’t that complicated to build I had some issues while trying to position all the items where I wanted to… The biggest issue of them all was positioning the rounded image as it kept getting over the text from the right… and this isn’t even working on all browsers, lol. Maybe it was a little waste of my time as I might need to recreate it without using clip-path
. :stuck_out_tongue:
Nevertheless it was an interesting project! (as most of the personal projects that I’m building are, haha).
You can find it live on Codepen.
Let me know your thoughts. What would you add to improve it? :grin: