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

  • 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 updateFilters() function to updateStateWithFilters() as it had the same name as the one in the Filter component. It was confusing.

    This function has changed, we pass it an object and use it to directy set the new state.

    Back to the Filter component, we populate the dropdown with a list of years from 1900 to the current year.

    For each onChange function (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 Discover for 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

    Bug!

    While browsing and developing the app, I noticed that my computer’s fans were going crazy.

    I decided to do a simple console.log of the API call and…

    Infinite loop 😍

    What’s happening here?
    This occurs only in the homepage /. Chances are that the bug is in Discover component.

    The problem is with componentDidUpdate(). Let’s break it down what happens once it’s called.

    componentDidUpdate()
       -> getMovies()
        -> setMovies()
         -> setState()
          -> componentDidUpdate() again 😱💥

    Each time the state is changed, componentDidUpdate() is called. And through getMovies and 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

    Small updates today:

    • Add sorting (popularity, rating, original title) and ordering (ascending and descending) options
    • Add icons for filters toggle buttons for a better experience
    • Remove item.title for imageless movies

    Reference commits: 935fdca, 9fa7f6e, 493e7e6

  • 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:

    Reference commits: 97ca15c, 18c153b, 19dfde8

  • 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 resetState() in Filters and the fact that I pass resetFilters() as props to Sidebar and Filters, I can actually reset the state. Great! 🎊

    For a better user experience I added an icon in the search field.

    Reference commits: b4d806a, 9ea2bec

  • 25th of August 2017 — 32

    In this step I made 3 changes:

    • Update <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 authenticated prop. Styles were also adjusted accordingly. Finally, I added authenticated: false to the global state in App and passed it as props to the Header.
    • Add Firebase configuration settings found in the Firebase console and install the NPM package.

    Reference commits: 83b7fa0, 0e20271, ec7ecf6

  • 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:

    • The Login component 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

    Reference commits: a74d276, ddbe606

  • 27th of August 2017 — 34

    First, in App.js we add a user: null to the defaultState. Then, we manage the state (wether the user is logged in or not) in componentWillMount here. We pass a prop to the Header component 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 authenticated: false and user: null to 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 defaultState too.

    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.

    Reference commits: 92010f7, 84263f3, be99f50

  • 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 UserMenu component 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: Header.

    Nice! 😇

    Reference commits: e5328e4, d1efe70

  • 30th of August 2017 — 36

    In App.js, I found the name for the defaultState confusing. 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 true by default.

    Then, I created a Loading component. 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 /popular, /top-rated, /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:

    1. 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.

    2. I added the Firebase authentication with email/password, following this tutorial by CoderJourney. It is well explained and fits my needs.

    3. 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.

    Reference commits: 043a114, 56ba4e1, 08cfa83, edfe8f2

  • 1st of September 2017 — 38

    [This doesn’t really count as a step but I’ll leave it here anyway]

    One month

    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 About or Info page 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. ¯\_(ツ)_/¯

    Reference commits: 0cadbd0, a1d86f8

  • 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: app.auth().currentUser.uid;.

    When the function is called, we update the favorites child 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.

    Reference commits: 8db811c, 6f0ee20

  • 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 update() and remove() is called.

    In this step, there is a big change in the List component.

    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 List component. It iterates over an array of movies and return some JSX code for item of the array.

    Let’s create a MovieItem component 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.

    In 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 Discover and Main, and inside them, we pass it down to the List and 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 <Route> element. Weird.

    Reference commits: 262c7c6, 3fd5a6a, 202037f

Photography: Lloyd Dirks