“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.
- 1st of August 2017 — 0
- 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
- 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.
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.logthe 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
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.
Once that I have an API key, I can finally retrieve some data by making requests such as:
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.
- 4th of August 2017 — 6
Let’s jump right into our Main component.
A lot of things have changed here:
- A new
apifolder 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
<Main />component was created and added to the
- For the first time, we are dealing with both state and props. The object returned from the API request is stored in the state. The prop
titleis used to display the title of the section. It’s passed from the parent component
- We fetch the data thanks to the API request and store the result in the state
- Finally, we use the lifecycle hook
componentDidMount()to retrieve the data once the component has been rendered for the first time
See the result in the console:
Reference commit: f2d011d
- A new
- 4th of August 2017 — 7
As I said before, we need a list of movies. We successfully retrieved a
popularMoviesobject which contains an array
resultscontaining several elements. Each of them is a movie.
In order to display those elements, we are going to build a
The parent container
<Main />passes the results of the API request as a prop to the
🤔 One thing I don’t understand.
You may have notice I left a
console.loghere. The output is the following:
So, the first output is an empty object for
popularMoviesand undefined for
- 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?
<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
Iván shed light on my questions.
There are two outputs because:
- The view is rendered the first time and there isn’t any movies (yet)
- Once we receive the API response, the view is rendered again
rendermethod 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
First, we rename all references to
popularMovieswith a more general name:
Then, in our
<App />component, we pass a prop
sectionwith the name of the section.
Finally, back in
<Main />, we pass the prop received from
<App />as an argument of our
Reference commit: c950c4e
- 6th of August 2017 — 10
Perfect. We can now use the
<Main />component multiple times by passing different properties.
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.
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>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
<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
If you remember correctly, we had stored the
searchTermin 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
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
Redirectfrom React Router.
This will be on a new route that we add to our
<App />component. This route will lead to our
<SearchResults />component, we retrieve the state via
🤔 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
<SearchBar />component doesn’t seem to redirect to the results page and this is what is causing
props.location.statenot 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.
<SearchResults />we can access the state thanks to
Why I had to change the route path from
/searchremains 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
searchTermto the state in the
The path that I had initially:
/search?query=:searchTermis 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
Reference commit: 2821a3d
I also replaced the spaces (if there are any) with a
+sign in the
- 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).
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
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
handleSearchSubmit()function so that we switch pages when searching for a movie. Also, no need to store the
resultin 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
<Main />. I added the
total_resultswhich 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_pathexists. 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
- I modified the
history.pushobject so that I can retrieve the
searchvalue in the results page
- I parse the query strings to get the
searchTermand add the
componentWillReceivePropslifecycle method so that when the component receive new props, it is rendered and load the data
This is nice. 😇
Reference commit: 45d32c0
- I changed the path of the route to be just
- 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
- 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.
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
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
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.
Passing the state
Appas a prop to
Filtersto 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
Discoverhad no way to communicate to
Filters. There is a way to achieve this though:
App, pass down a function as a prop to
Discover, call it inside discover on button click
Appruns that function when called, and changes
App’s state. When
App’s state changes, the prop sent down to
Filtersalso changes, because we passed it
App’s state in step 1.
- 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
Appand 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
Filterscomponent. It needs to be managed by
Apponly. The callback function
Filtersdoesn’t actually change the state, it needs to take the new data as an argument. And then, in the parent (
updateFilters()takes the new data and set the new state with it. The child
Filtersis directly calling that function, and inside it
thisis the parent’s instance.
Last but not least, we use
Discoverto 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
- 17th of August 2017 — 26
As expected, some refactoring is needed.
Here we will set up the Release Date filter. We don’t want a range here. A simple dropdown will be enough. I started installing a simple Dropdown component.
We set the default state to the current year here. For more clarity, I renamed the
updateStateWithFilters()as it had the same name as the one in the
Filtercomponent. It was confusing.
This function has changed, we pass it an object and use it to directy set the new state.
onChangefunction (in every filter), here and here, we create an updated version of the filters object. It receives the filters as a prop, so we can do an immutable update on that prop, and pass the updated version to
updateFilters. I have had a hard time with this part to be honest.
Reference commit: f56587e
- 18th of August 2017 — 27
Let’s add another filter called Runtime.
Now that we know how things work, it’s easier to add more filters.
I also cleaned up the messy API call in
Discoverfor something more readable.
I changed the defaults value for the filters state and added a small sentence to summarize what we are actually seeing.
This is looking good!
Reference commit: 1dbfaa5
- 19th of August 2017 — 28
While browsing and developing the app, I noticed that my computer’s fans were going crazy.
I decided to do a simple
console.logof the API call and…
Infinite loop 😍
What’s happening here?
This occurs only in the homepage
/. Chances are that the bug is in
The problem is with
componentDidUpdate(). Let’s break it down what happens once it’s called.
-> componentDidUpdate() again 😱💥
Each time the state is changed,
componentDidUpdate()is called. And through
setMovies, the state is changed. Hence the ∞ loop.
To fix it, let’s simply check if the props have changed.
Reference commit: f5b0e82
- 20th of August 2017 — 29
- 22nd of August 2017 — 30
Today, I added a new component for the
Footer. Since there isn’t much to put inside, it will fit nicely at the bottom of the sidebar. As stated here, there are some attribution requirements for the use of TMDb API. Let’s add the logo of TMDb and the notice they require.
Other than that, there have been small changes in the UI. I talked to my designer friend Denis about this project, he took a look and gave me some feedback and ideas. There will be no toggling for the filters, they will be located inside the sidebar (no overlay). This required a bit of refactoring but nothing really hard. Sorting and ordering will be out of the sidebar, next to the movies.
Here is what is looks like now:
- 23rd of August 2017 — 31
A nice feature to have after making a few changes to filters movies is the ability to reset filters to their initial values.
That’s what I did in this step. I moved the content of the state to a property initializer.
Again, thanks to a callback function
Filtersand the fact that I pass
resetFilters()as props to
Filters, I can actually reset the state. Great! 🎊
For a better user experience I added an icon in the search field.
- 25th of August 2017 — 32
In this step I made 3 changes:
<Movies />component styles: now, we use the movie backdrop image in full width, this looks way nicer.
- Add an (empty at the moment)
<Login />component to prepare for future implementation of the authentication. I also added a login button in the header with a conditional display through the
authenticatedprop. Styles were also adjusted accordingly. Finally, I added
authenticated: falseto the global state in
Appand passed it as props to the
- Add Firebase configuration settings found in the Firebase console and install the NPM package.
- 26th of August 2017 — 33
Here are the changes for this step:
- In the end, I wasn’t very fond of the login icon so I changed it. Here is the before/after:
Logincomponent has been updated with the following:
- A form with styles
- Multiple buttons for social media login options
- A look The Empire Strikes Back 🌟
A series of variables are needed to handle the Firebase authentication via multiple providers
- 27th of August 2017 — 34
App.jswe add a
user: nullto the
defaultState. Then, we manage the state (wether the user is logged in or not) in
componentWillMounthere. We pass a prop to the
Headercomponent to access the
user’s data. In the header, we add the user’s avatar and a link to
/logout. For now, clicking the avatar will log out the user.
In #31, we added a way to reset filters and move the content of the state to a property initializer. Now, I just added
user: nullto the
defaultState. I realized we can’t do this because they would be reset when we reset the filters. Instead, we add them to the state and use the spread operator to add
In order to test our authentication process, we need to be able to log out. So, here we create a Logout component. This component will sign out the user and redirect them to the homepage afterwards.
- 29th of August 2017 — 35
Now that the authentication with GitHub is done, I had in mind to work on the other social networks (Facebook, Twitter and Gmail). I took a look at Firebase documentation for managing all these providers (respectively here, here and here) and it turns out they all use the same pattern as GitHub’s.
So I made some changes to the GitHub’s authentication function to make it more generic and accept a provider as argument.
The other thing I did here was creating a
UserMenucomponent toggled when on avatar’s click. In here, we can put links to Favorite movies, the user’s watchlist and logout. The state of this component will be located in its nearest parent:
- 30th of August 2017 — 36
In App.js, I found the name for the
defaultStateconfusing. Since it just serves the purpose of setting the default state for filters, I renamed it to
defaulFilterstState. More explicit.
The goal for this step is to add a loading to provide a better user experience. In order to do this we must add it to the state as a boolean so we can activate/deactivate it depending on its value (true/false). I set it to
Then, I created a
Loadingcomponent. I called it in App.js depending on the state of
loading. It’s interesting to have this loading in several occasions: between the app checks if the user is logged in, when we wait for a response from the API in homepage (Discover component), in
/coming-soon(Main component), and while we are waiting for the search results.
Let’s keep moving! 🌀
Reference commit: 1809e61
- 31st of August 2017 — 37
What happens in this step:
Denis gave me its input on some elements of the app that were worth (re)designing. The sorting and ordering block was revamped, and the icon of the search input field was moved to the left.
I added the Firebase authentication with email/password, following this tutorial by CoderJourney. It is well explained and fits my needs.
While authentication with social networks brought data about the user, it is not the case with email/password authentication. That’s why I had to put a default avatar instead of the user’s avatar and display the user’s email instead of their name.
- 1st of September 2017 — 38
[This doesn’t really count as a step but I’ll leave it here anyway]
Wow, time really flies!
I started to build this app a month ago.
So far, this has been a lot of fun. I’ve had some frustrating moments but if I hadn’t, this would have been so boring 😝
Keeping this sort of progress journal is very helpful. Apart from actually noticing how time passes by, I can have a clear image of the evolution of the project. It motivates me to keep coding and finding new ways to improve the app.
Here is a small recap of the status of the project. What do we have in our hands?
- 4 different lists of movies with a route for each (Discover, Popular, Top Rated and Coming Soon)
- A sidebar displaying navigation and filters on specific page (Discover)
- A nice way of searching for movies, either with the Search field or with a simple URL (
/search?query=interstellar) and a page where we display results of a search
- Below each list, we can load more movies with a button that makes another API call for the next page of results
- Filtering (rating, date, duration) and sorting (ascending, descending) options for movies. Filters can also be reset to default settings!
- A loading state when the user waits for API response for example
- A single movie page with basic information of the movie (overview, poster and backdrop image)
- Authentication with email/password as well as social networks (Twitter, GitHub, Facebook, Google)
Where do we go from here?
- Building an
Infopage could be useful for people that stumble upon the app
- 404 page and component (?)
- Replacing loading with placeholders would be a nice touch
- Users should have the ability to add movies to Favorites, Watch Later (and maybe Custom lists?). This should be persistent of course, hence the use of Firebase database
- The single movie page looks too empty. We need more info and better design. Adding similar/related movies could be fun
- Caching requests would be very interesting
- Add Redux and refactor (a lot)
If you have more ideas, please go ahead!
- 4th of September 2017 — 39
I haven’t had much time to work on this project these days but I definitely don’t want to give up.
What I did here were minor design tweaks in the single Movie page such as changing the size of the poster, adding SVG icons to favorite the movie and play the trailer.
I also ditched Roboto for PT Sans. ¯\_(ツ)_/¯
- 5th of September 2017 — 40
Bit by bit, I’m slowly preparing the integration with Firebase: the features that will allow an authenticated user to add/remove favorite movies and add/remove movies to their watchlist.
Here, just like in the previous step, I added icons in the list component so that I can click on the heart to favorite the movie and test the feature.
Reference commits: cfc3423
- 6th of September 2017 — 41
Here we go with the function that will allow the user to add a movie to their favorite list.
For this, we need the ID of the current authenticated user:
When the function is called, we update the
favoriteschild object of the user with the ID of the selected movie.
Here is the result thanks to the magic of Firebase realtime database:
Also, I am adding the favorites movies to the state here from the database. Pretty cool. Not sure if this will be helpful at some point or if I will do things differently but here it is. It was interesting anyway to know how to read data from the database instead of just writing it.
- 9th of September 2017 — 42
Adding and removing movies to Favorites or Watch Later lists is nice. An even nicer thing to do is to let the user know what is happening when/after they clicked on the heart icon. So far, nothing happens, the user doesn’t know whether it worked or not.
That’s why I added a notification system that plays nicely with React because it was built specifically for it. Now, when an action is triggered, a callback function for
In this step, there is a big change in the
In the Discover view, I need to be able to detect if movies have been favorited or added to the watch list. And I need this for every single movie. To accomplish this, it’s better if movies are treated a separate component of their own. Here we see the previous state of the
Listcomponent. It iterates over an array of movies and return some JSX code for item of the array.
Let’s create a
MovieItemcomponent and pass it the props that we are going to need. This was highly inspired by this tutorial (step 18). Now we can work inside this component and modify its state upon user’s actions. We also render the heart icon wether the user is authenticated or not. If not, they will be redirected to the Sign Up/Log in page.
App, there a state object called
favorites(we will need this to check if a movie is inside the array/object of favorited movies). In order to use it, we must pass it down to child components as props. We pass it down to
Main, and inside them, we pass it down to the
Listand then finally to
MovieItem. Pheeeww. Same goes for other functions. It was a bit tedious and I’m not sure this is the best way to do/organize this because I now have a lot of repeated props for every