Day 34- Effect and data fetching in React-lesson 5.

Hello and welcome to day 34 of my challenge!πŸ˜ŠπŸ’•

Selecting a movie

Let's take our application to the next level by giving you the ability to select a movie and see more details. Imagine being able to pick any movie that catches your eye and instantly accessing more information about it on the right side of the screen. Plus, if you ever want to go back to the main view, it's just a click away.

To make this happen, we'll introduce a new feature: a state that remembers which movie you've selected. This state, residing in the main component (App.jsx), will store the unique ID of the movie you're currently exploring. Let's set it up and make your movie-watching experience even more interactive and enjoyable!πŸ’•πŸ’ͺ

The reason for storing only the ID and not the entire movie object is that the data we initially retrieve from the search is limited, containing only basic details like title, year, and poster. Additional details about the movie require another API call, which we trigger when a movie is selected.πŸ€”πŸ€”

Let's create a new component called MovieDetails to display the details of the selected movie. For now, let's simply display the selectedId passed as props.πŸ‘‡πŸ‘‡

I'll conditionally render the MovieDetails component if there is a selectedId. By the way, here are the props passed to the MovieDetails component.πŸ‘‡πŸ‘‡

For example, if I change the value of the state variable selectedID to an actual ID of one of the movies, the MovieDetails component will be rendered with the corresponding selectedID prop.πŸ‘‡πŸ‘‡

If you inspect the React Developer Tools and notice that the selectedID corresponds to the ID of the second movie.πŸ‘‡πŸ‘‡

Since the state variable selectedID is updated to the ID of the second movie, the MovieDetails component will display the details for the second movie. However, if the MovieDetails component is only returning the ID, then it will only display the ID of the selected movie, not the full details.πŸ‘‡

Next, we'll update the state when a movie is selected. This will be implemented in the Movie component, where we'll attach an onClick handler to the movie list item. When a user clicks on a movie, the corresponding ID of that movie will be passed to the state variable selectedID, triggering a re-render of the MovieDetails component with the details of the selected movie.

To create the function that updates the state when a movie is selected, we'll define a function called handleSelectDetails. This function will receive the ID of the selected movie as an argument and update the selectedID state variable accordingly.πŸ‘‡πŸ‘‡

Now, clicking on any item in the list displays its corresponding ID in the right box.πŸ‘‡

Additionally, let's implement the ability to close the selected movie details by resetting the selectedId to null. We'll create a function called handleCloseMovie to achieve this.πŸ‘‡πŸ‘‡

We'll pass this function as a prop to the MovieDetails component, where it will be invoked when the "Back" button is clicked.πŸ‘‡

Inside the MovieDetails component, we'll add a "Back" button that invokes the onCloseMovie functionπŸ‘‡

Finally, let's make the selected movie details section toggleable. If you click on the same movie again, we'll close the movie details. So I'm updating the handleSelectDetails function to accomplish this.πŸ‘‡


Loading Movie details

Let's load movie details for individual movies. This involves fetching the movie corresponding to the selected ID whenever the movie details component mounts. To achieve this, we'll use the useEffect hook with an empty dependency array to ensure it runs each time the component renders. We'll define an async function named getMovieDetails to handle the fetching process. Using the IMDB ID parameter from our API documentation, we'll fetch the data and log it to the console for now.πŸ‘‡πŸ‘‡

So, this time we successfully retrieve all the data about the movie at the console, which wasn't available when we searched for movies initially.πŸ‘‡

Now, our next step is to integrate some of this retrieved data into our visible user interface, within a visible section of the component. How do we accomplish this?

As usual, we begin by introducing a new piece of state: movie and setMovie. The default value for this state will be an empty object since the data retrieved from the API call is in the form of an object.πŸ‘‡

So, instead of logging this data to the console, let's update the movie state with this data. That is, setMovie(data) πŸ‘‡

And so, now we should be ready to use that data here in our JSX. So, actually, let's destructure now the object because I really don't like these variable names here all uppercase. I have no idea why they did it this way.

We'll destructure data from the movie object, assigning variables for the title, year, poster, runtime, IMDB rating, plot, released date, actors array, director, and genre. πŸ‘‡

Let's now actually use this data right in our JSX here.πŸ‘‡πŸ‘‡

And now, let's integrate the star rating component we developed earlier. We'll import the "StarRating" component and include it in the MovieDetails section.πŸ‘‡πŸ‘‡

If I attempt to select another movie here, such as this one, nothing happens initially. However, if I close this and then open up the second movie, for example, it works fine. But when I click on another movie, the same problem arises: the component does not update. This issue stems from our effect, which loads the movie data only when the component first mounts. Since clicking on another movie does not remount the component, the effect doesn't run again. To resolve this, we need to include the selectedId prop in the dependency array of the effect. This ensures that the effect runs whenever the selectedId changes, providing the desired functionality.πŸ‘‡πŸ‘‡

So, similar to our previous approach, what we want now is a quick loading indicator just to let the user know that something is happening. And so let's do that exactly as before. So we create a new loadingDetail state and then setLoadingDetail and we start with false .πŸ‘‡πŸ‘‡

And then immediately before we start fetching, we set LoadingDetail to true. And as soon as it is done, we set it back to false .πŸ‘‡

Finally, let's now utilize the LoadingDetail state in the JSX and conditionally render the loader when this is true.πŸ‘‡πŸ‘‡

Let's proceed to make our watched movies list functional.

Recall that we already have the watched state defined here, which is currently an empty array but was previously tempWatchedData πŸ‘‡

If we change it back to tempWatchedData, then our watched movies list will be populated with the initial data stored in tempWatchedData .πŸ‘‡πŸ‘‡

If you change it back to tempWatchedData, you'll have access to the previous watched movies list data stored in tempWatchedData πŸ‘‡

So, let's begin by creating the function handleAddWatch that allows us to add a new item to the watched array. This function will take a movie object as an argument. We'll then use setWatched to update the watched movies array. We'll get the current watched movies array, create a new array based on it with all its existing elements, and then add the new movie object to it.πŸ‘‡πŸ‘‡

As seen in the image below, each of these movies requires the following information: poster, title, rating, user rating, and runtime. Essentially, we need to create a new object for each movie, including these details, and then add each object to the watched array.πŸ‘‡πŸ‘‡πŸ‘‡

And now, let's pass the handleAddWatch function that we just created as the onAddWatch prop. We're passing it to MovieDetails because that's where we'll have the button to add the movie to the watched list.πŸ‘‡

Okay, let's move to the MovieDetails component and add a button below the rating section. We'll create a button element with the class name btn-add and the text "Add to list".πŸ‘‡πŸ‘‡

ResultπŸ‘‡πŸ‘‡

Now, we need an event handler for this button. Let's create a new function called handleAdd. This function will call the onAddWatch prop that we passed into the component.πŸ‘‡

So for the newMovieDetails object, we need to include the userRating field as well ln a bit. πŸ‘‡

Now, I add the onClick event listener to listen for the click event on the "Add to list" button.πŸ‘‡

Result: Now, we get an error because we haven't included the userRating.

So after adding a movie, we also want to immediately close the movie details because I had to use the back arrow to get here.πŸ‘†πŸ‘†

So this works fine now, closing the movie detail when a movie is added to the watched list.πŸ‘†πŸ‘†

So essentially, we aim to allow users to input their rating for the movie.πŸ‘‡

So essentially, we want to capture the user's rating input and use it when adding a movie to the watched list. This means we need to store the rating state within the MovieDetails component instead of solely inside the StarRating component. To achieve this, we need to revisit the StarRating component and create a mechanism for passing the rating state outside the component.πŸ‘‡πŸ‘‡

We created a way of accessing that state outside the component by defining a function called onSetRating, which allows us to set the rating state from within the StarRating component. Now, let's return to the App component and set up a state for the userRating. Then, we'll include the userRating in the newWatchedMovie object.πŸ‘‡πŸ‘‡

Let's retrieve the setUserRating function and pass it to our StarRating component using the onSetRating prop.πŸ‘‡πŸ‘‡

This works fine now. I added a rating of 6πŸ‘‡

And now that we have this userRating, we only want to allow a movie to be added to the list if the user actually gave it a rating. So let's translate that requirement into code. Essentially, if userRating is greater than zero, then display the button.πŸ‘‡πŸ‘‡

I added some CSS styles and changed a few😘

To be continued..

Thank you for studying with me πŸ˜ƒπŸ˜ŠπŸ’ͺ.

Β