🚀 Check out my $1,000,000 journey!

Creating a Movie Search App with React

8 min read

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:

Movie Card

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:

  1. 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 the render method. Without doing so, movieData would be undefined and it will trigger an error.
  2. If Poster doesn’t have a value or if it’s value is N/A we return null. This will ensure that in this case the MovieCard will not display anything. We wouldn’t want to see an empty card, do we? :stuck_out_tongue:
  3. We set the Poster as a backgroundImage of a div. This is because we’ll use clip-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: )
  4. 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.
  5. 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 be mapped 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:

  1. To successfully call the API endpoint you’ll need an apikey. You can get one from the OMDb website.
  2. Besides the apiKey we also pass the IMDB movie ID as the i= query parameter. This value is coming from this.props meaning that when we’ll use the <MovieCard> we’ll have to pass the movieID as a prop. You’ll see what I mean in the next section. :blush:

The MovieList Component

This component will do the following:

  1. Provide a form with an input that will be used by the user to submit a search term.
  2. 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.
  3. 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.

  1. the onChange on the input - this will call the handleChange method which will update the searchTerm state with the input value every time the input is changed.
  2. the onSubmit on the form - this will call the search method which will make a request to the API endpoint providing the searchTerm as a query parameter. It will save the response data into the moviesList array (Note that we only save the imdbID values from each returned object because this is all we need to pass to our MovieCard 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:

    Want to stay updated?
    Sign up for the newsletter.

    No spam. Unsubscribe at any time.