React Movie App

“What for? There are already a million apps like this out there”. Well, the only way to master something is to learn by doing. And for that I need to practice, practice and practice again. Let’s build stuff!

Below is a list of micro posts that describe the progress of the project.

🎬 Check out the app 🎬 Check out the code

  • 1st of August 2017 — 0

    Alright, first we need a name for the app. Let’s call it Eiga. We will keep it simple and start the project with Create React App. Here we go: create-react-app eiga and cd eiga and finally npm start.

    We initialize the repository, we push it and we are ready to start. 🍿

  • 1st of August 2017 — 1

    What do I need to really get started?

    • Choose a public API to tinker with
    • A basic idea of what the app will looks like, its general features

    So, I had a look at this collection of public APIs and I decided to go with TMDb.

    Regarding the visual guide of the application, I made the following wireframe with Moqups. Not much but it’s a start. This is heavyly inspired from this Dribbble shot that I liked.

  • 2nd of August 2017 — 2

    Let’s start with our first component: the header. For now, nothing really fancy here. A simple dumb and stateless component with the logo of our app. The search bar and the avatar/login area are still missing.

    Now, let’s deploy with Firebase and check the app at:
    https://eiga-de72c.firebaseapp.com/

    Update

    I added a more friendly URL in Firebase Hosting setup: eiga.macx.im

    Reference commit: 1d66024

  • 2nd of August 2017 — 3

    Now, let’s build the Search Bar.

    As you might have guessed, the search bar is used to search for movies. The search text (the value of the input field) belong to the state since it changes over time. We are going to need this value elsewhere.

    Unlike the Header component, this one is not a presentational one but rather a container component.

    Since we are not working with the API yet, I’ll just console.log the value. Here is what we have at this point after entering something in the search input field and hitting enter.

    Note: Regarding the styles, I chose to keep it simple for the moment and just import a CSS file for each component. No CSS-in-JS then. For this project, I’ll focus more on the JS/React part.

    Reference commit: f2f4e75

  • 3rd of August 2017 — 4

    Next step is the Sidebar component.

    At this point, everything is still static. The sidebar, which will allow the users to navigate through the app is made up a list of links. No state then for this component.

    I added the Sidebar to the main App.js file and with a bit of CSS, this is how it looks. The Popular item is the active one, as it will be the starting point of the app (for now, at least).

    An interesting question: how will we be able to change the active class on the correct item if the user goes to /top-rated for example?

    Reference commit: b9d91d7

  • 3rd of August 2017 — 5

    Alright. I will stay with this static sidebar for the moment. I will go back to it another time/day.

    Now, let’s focus on the API. To use it, I had to signup for an account at TMDb and request an API key. Be sure to read their API FAQ if you want to reproduce my steps as you follow my journey.

    Once that I have an API key, I can finally retrieve some data by making requests such as:
    https://api.themoviedb.org/3/movie/550?api_key=abcd09876dcba1234

    Let’s work now on our <Main /> component. This component will serve as a container for the list of movies as described in step 1.

    I need to retrieve a list of current popular movies on TMDb.

  • 4th of August 2017 — 6

    Let’s jump right into our Main component.

    A lot of things have changed here:

    • A new api folder was created with a file inside containing variables related to the API. I chose to separate this information as we might need it and use it in multiple files
    • The <Main /> component was created and added to the <App /> one

    Inside the <Main /> component:

    See the result in the console:

    Reference commit: f2d011d

  • 4th of August 2017 — 7

    As I said before, we need a list of movies. We successfully retrieved a popularMovies object which contains an array results containing several elements. Each of them is a movie.

    In order to display those elements, we are going to build a <List /> component.

    The parent container <Main /> passes the results of the API request as a prop to the <List /> component.

    Then, we iterate over the list prop (which is the results array passed by <Main />) in the <List /> component thanks to .map.

    And… voilà:

    🤔 One thing I don’t understand.

    You may have notice I left a console.log here. The output is the following:

    So, the first output is an empty object for popularMovies and undefined for results.

    • Why is that so?
    • And why does it appear twice in the console?
    • Is it because of the small delay between the request and the retrieval of the data?
    • Is <Main /> rendered twice then?
    • Is it bad? Should it be avoided and how?

    If anyone has the answers to these questions, do tell me please. 🙏

    Reference commit: 3a34b25

    Update

    Iván shed light on my questions.

    There are two outputs because:

    1. The view is rendered the first time and there isn’t any movies (yet)
    2. Once we receive the API response, the view is rendered again

    The render method is triggered every time something changes in the view. Thanks to React’s Virtual DOM, only the objects that have actually changed are updated.

  • 5th of August 2017 — 8

    Good. We are slowly getting there. 😜

    Now, we could duplicate our <Main /> component and repeat the same pattern for the Top Rated and Coming Soon sections. The thing is that wouldn’t be very DRY.

    The requests that are made to get movies for all these three sections (and probably more) are very similar. Only a few parameters change. The problem is that <Main /> is very tight to a specific thing: rendering popular movies.

    💡 The idea is to refactor getPopularMovies() to a more general function and pass it arguments depending on which section we want to reach.

  • 5th of August 2017 — 9

    Let’s refactor <Main />.

    First, we rename all references to popularMovies with a more general name: movies.

    Then, in our <App /> component, we pass a prop section with the name of the section.

    Finally, back in <Main />, we pass the prop received from <App /> as an argument of our getMovies() function.

    Reference commit: c950c4e

  • 6th of August 2017 — 10

    Perfect. We can now use the <Main /> component multiple times by passing different properties.

    Let’s do it.

    It works great. The only issue is that we now have 3 lists of movies on the same page. That is not what we want. We want to display a list for each section inside its own route.

    How can we manage that?

    Reference commit: 965278d

  • 6th of August 2017 — 11

    Here comes React Router to the rescue.

    ℹ️ First, let me tell you that I found this tutorial interesting to get started with React Router.

    Alright, let’s install React Router with npm install --save react-router-dom.

    Then, we wrap our main code in <App /> with <BrowserRouter> and build our routes with our 3 <Main /> components.

    In our browser, when typing the routes manually, we can check that it’s working just fine 👌

    What about the navigation in the sidebar? Links are working as well but when we click, it’s refreshing the entire page. 😕 We do not want this.

    Reference commit: 5f43539

  • 6th of August 2017 — 12

    To avoid this unpleasant refresh, we are going to use React Router’s <Link> component. Well, actual <NavLink>:

    <NavLink> A special version of the that will add styling attributes to the rendered element when it matches the current URL.

    That’s exactly what we need. Let’s replace those <a> with <NavLink>. As expected, when we click on an item in the navigation menu of the sidebar, only the content that has actually changed is updated. Yay! 💥

    Reference commit: c31370c

  • 7th of August 2017 — 13

    Now that we have functional lists for different sections (Popular, Top Rated, Coming Soon), it’s time to go back to our <SearchBar /> component.

    If you remember correctly, we had stored the searchTerm in the state. Good. Now, we want to use it to make our request to the API. We will pass it as and argument to the function that makes the API call. Then, when we submit the form, we call that function passing it the searchTerm.

    Here is the result:

    How cool is that?! In the next step we will use this data to display the results. 🤗

    Reference commit: 9b209da

  • 7th of August 2017 — 14

    Once the form of the <SearchBar /> component is submitted, we want to redirect the user to the Search Results page. We do this with Redirect from React Router.

    This will be on a new route that we add to our <App /> component. This route will lead to our <SearchResults /> component.

    Inside the <SearchResults /> component, we retrieve the state via props.location.state.

    Boom! 💣💥

    🤔 There is an issue though

    While this will work perfectly the first time, if we type again in the search field from the results page, nothing will happen. So far, I haven’t been able to solve this. I think this have to do with <Redirect> and props.location.state. The <SearchBar /> component doesn’t seem to redirect to the results page and this is what is causing props.location.state not to be changed. Still looking for a solution.

    Reference commit: 6139dcb

  • 8th of August 2017 — 15

    The following is an attempt at fixing the search issue that I described in the previous post. Even if there are things that I don’t fully understand, it seems to work now.

    It turns out <Redirect> wasn’t the best way to achieve what I had in mind. A better way is to use history.push with withRouter. Check the code for the <SearchBar /> component.

    Then, in <SearchResults /> we can access the state thanks to location.

    Why I had to change the route path from /search?query=:searchTerm to /search remains a mistery 🤷🏻‍♂️.

    The route was completely ignored. It wouldn’t go to SearchResults. Again, if anyone has an explanation, I’m all ears 🙋🏻‍♂️.

    Reference commit: 3bc23b9

  • 8th of August 2017 — 16

    I managed to pass the searchTerm to the state in the <SearchBar /> component.

    The path that I had initially:/search?query=:searchTerm is back 🤷🏻‍♂️.

    The only issue now (there’s always one) is that if a user goes to /search, they will get an error as there is no state for location.

    Reference commit: 2821a3d

    Update

    On madx`’s advice I finally decided to remove the searchTerm from state and use the data from the route: match.params.searchTerm instead.

    I also replaced the spaces (if there are any) with a + sign in the searchTerm.

  • 9th of August 2017 — 17

    Regarding the list of movies, there is one missing feature from the wireframe I did in #1: the button to load more movies (more pages, actually).

    Let’s do it!

    First, we create a button component with a few props for the text, the class and the onClick handler.

    Second, let’s take a look at the API, we can see that the response from the getMovies() request is the following:

    It returns more than the array of results. There are also the page, the total_results and the total_pages. We are going to use this to load more movies. We will need two more constants in our api.js file.

    Third, we adjust the API call with these new parameters.

    Finally, the most important part is in the setMovies() function. We get the results and the page data from the request, we check if there are already results: if the page is different from 1 (the first page), there has to be results already in our state. Then, we use the array spread operator to concatenate the old results with the new ones and ultimately we update the state. 😵 That was kind of a lot to process.

    The best part is that this change is applied in all sections (Popular, Top Rated, Coming Soon) thanks to the reusability of React components.

    Now we have another issue, this is not working for the search because the API call isn’t made in <Main /> but in the <SearchBar /> component. And we didn’t change anything there.

    Reference commit: ecb238f

  • 10th of August 2017 — 18

    Let’s modify things a bit to load more pages in the search results.

    First, we are going to move the API call to <SearchResults />. We move the history.push to the handleSearchSubmit() function so that we switch pages when searching for a movie. Also, no need to store the result in the state anymore.

    Second, we transform the <SearchResults /> component into a class because it will handle state now. We adapt the getSearchMovies() function and copy and adjust to our needs the setMovies() function from <Main />. I added the total_pages and total_results which can be useful in a Search Results page.

    We adapt our render function and add our <Button /> component passing it the right props, and voilà 👍

    Reference commit: 7273635

  • 11th of August 2017 — 19

    There is one thing that has been a bit annoying while building this so far: some movies don’t have an image. In this step we are going to fix this.

    With a simple conditional statement with the ternary operator, we check if the poster_path exists. Here we go:

    Reference commit: 578e5e2

  • 12th of August 2017 — 20

    Another thing that I wanted to implement was triggering the search via the URL, so that if you type: /search?query=somemovie, you automatically go to the <SearchResults /> page where the component is mounted and the search results displayed.

    For that, I made three changes:

    • I changed the path of the route to be just /search (again)
    • I modified the history.push object so that I can retrieve the search value in the results page
    • I parse the query strings to get the searchTerm and add the componentWillReceiveProps lifecycle method so that when the component receive new props, it is rendered and load the data

    This is nice. 😇

    Reference commit: 45d32c0

  • 12th of August 2017 — 21

    Well, here we are. It took 20 steps to get a functional application of the mockup I designed in step #1. There are still some elements missing but most of it is in place and most importantly, it works™. Let’s recap, we have:

    • Lists for 3 different pages of the app: Popular, Top Rated and Coming Soon
    • A persistent sidebar in which we can perform a search for movies anywhere
    • A way of searching for movies with a simple URL
    • A page where we display results of a search
    • Below each list, we can load more movies

    Isn’t it a good start? You bet it is 🤓

    Now, here are some ideas of features that we could implement to go further:

    • Filtering and sorting options for movies (rating, date, etc.)
    • Add loading when loading movies
    • Single movie page
      • Use as much as data as possible from the API
      • Similar/Related movies
    • Authentification
      • Profile page
      • List per user (Favorites, Watch Later, Custom list?)
  • 13th of August 2017 — 22

    Let’s keep it up! 🔝 I decided to go with the dedicated page for a single movie.

    First, we need a link to a specific movie. Let’s add a link in the <List /> component. I want to use the title’s movie in the URL so it needs to be URL-friendly (no spaces, lowercase).

    Second, we need a route. As I just explained, I will use the title of the movie, but this isn’t enough, let’s put the ID as well.

    Third, once this is all set, we need a <Movie /> component to actually display the movie. We make a specific API call, we get the data and we display it in our render() method. For the moment, I just used a few things like backdrop_path,poster_path,title,overview.

    Reference commit: 0e8d77a

  • 14th of August 2017 — 23

    Now, let’s go with the filters.

    As I said before, the basic idea is to filter movies with multiple parameters: date, rating, genres, release date, etc. It seems to me that it would be cool to have some sort of range to be able to filter movies from 2004 to 2008, for example. Let’s install react-input-range and build our <Filters /> component.

    For now, a very simple rating input range (from 0 to 10) will be enough. We will add things gradually.

    Reference commit: 0f31bdf

  • 15th of August 2017 — 24

    According to the API’s maintainers, it turns out it’s not a very good idea to filter and sort pre-made “featured” lists such as Popular, Top Rated and Coming Soon. Instead we will use Discover which supports a nice list of sort options.

    So, we build a Discover component. In this component there is a button to toggle the Filters bar. This was not an easy thing to do. I had to ask around in the Reactiflux chat.

    Passing the state filtersOpen from App as a prop to Filters to toggle a CSS class was quite easy. The hard part was the interaction between 3 components (App, Discover and Filters), because the toggling action must take place inside Discover, where the button is. But Discover had no way to communicate to Filters. There is a way to achieve this though:

    1. In App, pass down a function as a prop to Discover
    2. In Discover, call it inside discover on button click
    3. App runs that function when called, and changes App’s state. When App’s state changes, the prop sent down to Filters also changes, because we passed it App’s state in step 1.
    4. Rejoice! 🎇

    Reference commit: de46afb

  • 16th of August 2017 — 25

    The goal in this step is enabling the rating filters. The default rating is from 0 to 10. It is passed as props to the API call in Discover. What we want is that everytime we change these values, it should update the state of App and therefore update the props passed, calling the API with new parameters.

    I used the same method as in #24: passing a callback function.

    We don’t need the state directly in the Filters component. It needs to be managed by App only. The callback function updateFilters() in Filters doesn’t actually change the state, it needs to take the new data as an argument. And then, in the parent (App), updateFilters() takes the new data and set the new state with it. The child Filters is directly calling that function, and inside it this is the parent’s instance.

    Last but not least, we use componentWillReceiveProps in Discover to call the API again once the new props are received.

    Now, I’m pretty sure we are going to have to refactor because we will use multiple filters, not just ratings.

    Reference commit: 0361ecf

Photography: Lloyd Dirks