Photo by Lautaro Andreani on Unsplash
Day 40- Custom Hooks,Refs and State
Hi and welcome to day 40 of my 100 days of code challenge!❤
Rules of Hooks in practice -2
Yesterday, we discovered that React distinguishes each hook solely by its order number, without considering any assigned names or identifiers.
Now, let's return to the MovieMania
application and push the boundaries of hook usage even further! 💪😃
In the MovieDetails
component of the MovieMania
App, we have six hooks, as seen below in the React Developer Tool.👇👇
If we were to call a hook outside of the top level, essentially invoking it conditionally, it messes up the sequence, causing problems. That's exactly what we're trying to do now.👇👇
If the IMDB rating is over eight, we want to make two new variables, IsTop
and SetIsTop
, set to true using useState
.
When we save, VS Code warns us that using useState
conditionally isn't allowed, highlighting the importance of maintaining hooks at the top level of the functional component.👇👇
Now, let's see what happens when we apply this to "Interstellar" with a rating (imdbRating
) greater than eight.
Clicking on the first "Interstellar" from the search results triggers an error because the IMDB rating for this movie exceeds 8, and we attempted to create a new state when this condition is true.👇👇
So, now we can clearly see that the order of our hooks has changed, which React doesn't like at all.👇👇
Another thing to watch out for is using an early return. For example, if we check if the IMDB rating is greater than eight, and if it is, we return some JSX saying "Hi there".👇👇
But when we try this with "Interstellar" again👇👇
But when we click "Interstellar" again, we encounter the same problem. However, this time, the error informs us that fewer hooks were rendered. This is because at this moment, we only have the movie and its three states. Consequently, the three effects that were previously created are no longer present.👇
Due to the early return, those three effects that we had are no longer created.👇👇
More details on useState Hook
I'd like to revisit some important points and highlight one very important detail, which is the fact that these initial values that we pass into useState only really matter on the initial render.
Let's look at an example to illustrate this👇👇. Say we want a state variable called isTop
to be true if the IMDB rating is greater than eight. We might think this approach would work, but as we've learned previously, it won't.
Let's search for a movie as usual. However, when we check the dev tools, we notice that is indeed false, even though the rating for "Interstellar" is actually greater than eight.👇
Looking at our list of hooks, we also notice that this one is set to false. Why is that? It's because of what I mentioned earlier: whatever we pass into useState is the initial state. React only looks at this initial state during the initial render, when the component first mounts. However, during the first mount, the IMDB rating will still be undefined.
So, during the first render,
isTop
is false because the IMDB rating is undefined. It will remain false indefinitely because we don't update the state anywhere. Even during the second render when we finally receive the movie data, the state won't be updated again. Therefore, it will continue to stay false forever.
One way to fix this issue would be to utilize a useEffect
hook. We can achieve this by using useEffect
and passing in a function. We want this effect to run each time the IMDB rating
updates. 👇👇
And so, in this case, this should then work.
Let's see.👇
If we take a look again at our different hooks, we can observe that now indeed isTop
is true, indicated by our fourth hook.
This change is simply because of this useEffect
👇
In this scenario, using a state might not be necessary at all. If this functionality is what we truly desire, then what we should opt for is derived state. Instead of creating a real state with the useState hook, we should simply calculate the value directly.👇
Cleaning our console. And indeed, it is true.👇
This happens because this variable here is regenerated each time the function is executed, which occurs after each render.
This showcases the power and one of the great advantages of derived state—it updates basically as the component gets re-rendered..
Understanding React's asynchronous state updates
When you call a state updating function in React, such as
setState
in a class component or the state setter function returned byuseState
in a functional component, React doesn't immediately update the state and re-render the UI synchronously. Instead, React schedules state updates to be processed asynchronously.
Alright, let's proceed by creating a new state for displaying the average of the ratings we provided (userRating)
and the IMDB rating when we add a new movie to our watch list.👇👇 so that is avgRating
.👇
In the JSX, we include {avgRating}
to display the average rating directly in the component's UI. 👇
Instead of closing the movie immediately we click on "Add to list button", I'm going to comment out the onCloseMovie
function and add the updater function setAvgRating
.👇
Let's say that initially, we want to set it to the actual current IMDB rating, which is a string. So, let's convert that to a number. We'll use imdbRating
, and now, let's observe what happens if we alert this. This should make the behavior more visible.👇
And so the averageRating
should now be 8.7 because we set it to the imdbRating
, which is 8.7.👇👇
Let's observe what happens. Well, it's still zero when I try to add it to the list.👇
And that's because the state is set asynchronously here. In other words, we don't immediately get access to the updated state right after calling the state updating function. React will update all state and re-render the UI only after it's done processing this event handler (handleAdd)
. So after clicking Ok👇👇👇
And so let's now attempt to calculate the average.👇
So, let's say we set the userRating
to 10 and click "Add to List". We get five, and this five here is really just 10 divided by two.
The reason for that is that the averageRating
is still at zero. So, zero plus 10 divided by two equals five.👇
The avgRating
is still at zero because the state update is asynchronous. Even though we've called setAvgRating
before at line 339👇👇, React doesn't immediately update the state and make it available for use in the subsequent lines of code. Therefore, at the point where we calculate new average rating, the state update might not have been processed yet, and avgRating
remains at its initial value of zero until the next render cycle.👇👇
It's asynchronous state setting, which means that at this point, avgRating
has not been set yet and remains at zero, which is the initial value.
This situation leads to what we call "stale state" at this point.
But fortunately, we already know how to solve this, which is by passing in a callback function. This callback will get access to the current value of the state and ensure that we're working with the most up-to-date state.👇👇
This time what happened is that the avgRating
was set to the imdbRating
, which is 8.7. Then, in the next line, we immediately got access to that new value. Consequently, the addition of 8.7 and 10 gave us the correct average(9.35)
.👇👇
All right, and now there's just one final thing to learn about the useState
hook, which is that besides using a callback like this to update state, we can also use a callback to initialize state and I'll look into this tomorrow.
To be continued.
Thank you for reading💪