Day 34- Effect and data fetching in React-lesson 5.
Hello and welcome to day 34 of my challenge!ππ
Table of contents
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 πππͺ.