“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