In this chapter, we're going to go ahead and wrap up what we started in the previous chapter. One thing that's missing is infinite loading. So let's do a short chapter on how to do that. We already have the backend part ready. Now it's time to implement the UI part.
Besides that, we're also going to add suspense and error boundaries to our videos section component. And we're going to create a reusable infinite scroll component. And we're also going to demonstrate how it works. So let's go ahead and do that first. Let's go inside of our videos section.
And what I like to do here is rename this to videos section suspense. And then export comes to videos section like this. And then what I can do here is I can return the suspense from react and error boundary from react error boundary and then finally videos section suspense. Let's give this a fallback of error and let's give this a fallback of loading. Like this.
Nothing much should change but when you do a hard refresh here you should now see loading for a few seconds. Now let's go ahead and let's actually implement the infinite scroll component. In order to do that we first have to implement a reusable hook called useIntersectionObserver. So use hook called useIntersectionObserver. So useIntersectionObserver.ts.
Let's import useEffect, useRef and useState all from React. Let's export const, use intersection observer, and let's go ahead and define the options to be intersection observer in it. So this is globally available type. Inside of here, we're going to prepare our state to tell us whether we are intersecting or not. So isIntersecting, setIsIntersecting.
Basically we will use the observer to detect if the user has reached the bottom of a list so that we can safely initiate a function call. Let's also define a target ref to be useRef and let's add HTML div element here. Let's call the use effect. Let's define the observer here to be new intersection observer. Execute it and then inside of its method here go ahead and get the entry.
Return the arrow function here and simply do set is intersecting to the entry is intersecting, like that, and passing the options here. The options will be our optional prop coming from here. If target ref dot current, If we have the target set, right? So we are going to add an empty div at the end of our list. For example, videos section.
Imagine that at the end of this list, we have a little div with the ref target ref, right? And then we have our use intersection observer hook here. So basically the intersector will look if we have reached this with our UI. So for example on this screen it will immediately call the next request because it will be immediately rendered here. But if I was you know zoomed in or on mobile I would have to scroll and then we would see that little target ref.
So you can remove it for now. This was just a demonstration. So if we have that target ref, we are just simply going to tell the observer to observe the target ref dot current here. And of course, we have to have a proper cleanup method here to disconnect the observer so we don't cause any overload here like this and pass in the options as the dependencies here. And you can return back the target rough and is intersecting.
Like this, there we go. Now that we have the use intersection observer, let's go ahead inside of our components and inside of here we're going to create a reusable component called infinite scroll dot TSX. First of all, these are the props we are going to have. So interface infinite scroll props. Optional is manual boolean, has next page boolean, is fetching next page boolean and fetch next page method.
Now let's export const infinite scroll and let's simply assign the props here like this. Let's go ahead and destructure all the props and set is manual to be false by default. Inside of here what we are going to render is a div with a class name of flex, flex column, items center, gap four and padding four and then we're going to render our very simple ref which will be the target ref and we will give it the class name height of 1 So now we have to add our useObserver, what did we call it? UseIntersectionObserver. So our useIntersectionObserver hook from hooks useIntersectionObserver.
And from here we can get the target ref and then we can pass that here. There we go. So this is what we are going to try and detect if we've reached it or not. So instead of here what we can do is we can pass in the threshold if we want to modify it, for example, 0.5 and root margin to be 100 pixels. This is the configuration that I found to be working quite well with my infinite load needs.
And now let's add a use effect method here in which we are gonna have to decide whether to call fetchNextPage or not. So if we happen to be intersecting, which we can return from here is intersecting like this. So if we are intersecting and if we have next page and if we are not already fetching the next page and if we are not set to is manual only then are we gonna decide to fetch next page and we also have to add all these items here so is intersecting has next page is fetching next page is manual and fetch next page all of them right so make sure you didn't forget any exclamation points or accidentally added some exclamation points here great so we have that now and now what I'm gonna do is the following I'm gonna check if as next page in that case we will render a button component from UI button like this. Let me just change this to components UI button. Inside of this button, we're gonna check if is fetching next page.
We're going to render loading, otherwise load more. Variant will be secondary. Disabled will be if it has no next page or if it's fetching the next page. And onClick here will manually call fetch next page. So this will be kind of like a fallback in case the intersector for whatever reason doesn't work.
The user will always be able to manually click load more, right? But that's only if our back-end returns that we have next page. Otherwise, we will return a paragraph, which will say, you have reached the end of the list. Class name, text extra small and text muted foreground. Like this.
And now we can go inside of the videos section and we can add the infinite scroll component. Now this infinite scroll component of course needs its props. So let's go ahead and do the following. Besides data, we're going to get the query. And then inside of here, we can pass in as next page to be query has next page is fetching next page the query is fetching next page and fetch Next page will be query fetch next page.
And for now, what you can do is you can also add is manual. Just set it to true. This will automatically fall back to true. So now you will be able to see the button load more. And when you click load more, it will load more until it reaches the end of the list.
The load more will of course depend, if you don't have it, it means you probably don't have more than five items in your database. So you just go ahead and spam the create a few times of course until you have more than five items. Let's see what the error is here. Okay, if this happens to you, remember we did set up our... Where is it?
In my protected procedure, I have the rate limit here. So if you want to, you can increase this to a hundred requests in 10 seconds and then try again, there we go. So you should be able to now test out your infinite load here. And what we're going to do now is the following. Let's go back inside of our video section and this time let's remove IsManual.
Let's see what's going to happen. So I'm going to refresh the entire thing and you can see how it automatically starts loading. So that's how our infinite load is going to work. If the observer detects this, it's automatically going to call it in the use effect. But if for whatever reason it fails, unless it has loaded every single data possible, which our back-end will ensure that it does, the user is always going to have that button.
But if for whatever reason we want to turn off the observer, we just set isManual to true and then it will work like this by the user clicking on this. Great, so we are finished with that. And I actually want to leave it at this. I'm trying to think of the best course of action for our next chapter at the moment. I think that the next chapter should be starting to implement mux, actually connecting to mux so that we can have some proper videos, some proper thumbnails, and all of those useful things so that we can actually render this data here in a nicer way.
But there are some things that we can already do here. Let's go ahead and let's go back inside of our studio UI and let's go inside of view, studio view right here. And how about we give this div a class name of flex, flex column, gap Y of six, padding top of 2.5. Then inside of here add a div with a class name px of four and an h1 element of channel content like this. Below that a paragraph which will say manage your channel content and videos.
Let's go ahead and give the h1 element a class name of text to excel and font bold and let's give the paragraph here a more of a secondary class like text extra small and text muted foreground like this. So now you should have channel content written right here. If you ever get this type of error you can just reload your window. This seems to be the TypeScript server going wild. Let's go inside of the videos section and let's actually create a nicer display of this data here rather than just stringifying it.
In order to do that we're going to have to import the necessary components from our table. So ensure that you have add components UI table as I do source components. My, yeah. What can I close everything else? Close others.
My apologies, not this one. Close others. Like that, there we go. So inside of my components UI, you should have the table component here and you should be able to add all of these imports. And now what we're gonna do is the following.
We are gonna go ahead and give this div here another div with a class name, border on top and bottom. And then inside we're going to render a table, table header, a table row here and then a table head. This table head will simply say video first here with a class name PL6 and width of 510 pixels. After that, the next one will be visibility, which is not gonna have any special classes. After visibility, we're gonna have status, then we're gonna have a date, then we're gonna have views, comments, and likes.
And for these last three, we are gonna have a class name, text right. And the last one, we'll also have pr6. So right now you should have this table here video visibility status date views comments and likes. Now let's go ahead and remove the JSON stringify here and while we are inside of the table, outside of the table header, add a table body here and inside of here we're going to go over our videos. So let's go ahead and rename this from data to videos.
And what we're gonna do is we're going to combine all the pages available. So videos.pages.flatmap, get the page, page.items, and then .map and get the individual video. Like this. We're going to use the link component here and give this an href of slash studio slash videos and then video id And the key will be video ID as well. And then inside of here, we're gonna add a table row and This will have a class name of CursorPointer.
And instead of a table row, we can just add a table cell here, which will render video.title for now. So just make sure you have added the import for link from next link, like this. And we also have to, Yeah, the infinite scroll can actually be outside of the table. It doesn't have to be inside. And you can now see that we have some items here.
It's not exactly perfect. And there are some errors going on around here. So we are gonna go ahead and just resolve this now. So let's see what exactly is happening here. So inside of this for each video we are rendering a table row, we're rendering a table cell.
So how about we add you know some more table cells here. So we need a table cell for visibility, which right now doesn't exist, right? Then we need one for status, I believe. Then we need one for date. Then we need one for views.
Then we need one for comments. And I believe the last one is for the likes. All right, so now we can see untitled visibility, status, date, views, comments, likes. All right, and let's just see if I'm doing something incorrectly here. I think that I might be missing something.
Let's see, because this does not look... Oh yes, I think that our link needs to have legacy behavior on. There we go, yeah. If you had legacy behavior, then you can use link to wrap your table row. Right.
In case you cannot put legacy behavior on, there is a solution. Well, you're going to have to remove the link wrapper. So you're just going to have the table row. So go ahead and add a key here, which will be video dot ID. And then you're going to have to add the router from use router.
From next navigation, so you didn't have to do this. I'm just showing you in case you get stuck. You would have the router and then in here on click, you would do router.push, slash studio, slash videos, and then video ID, which will eventually go to the same thing, which right now is 404. The router.push is slower than link, because link will prefetch the route. So I'm going to roll this back without using userRouter, and I'm simply going to use the link wrapper with my legacyBehavior prop, because without LegacyBehavior, it breaks the component.
So I'm just gonna add LegacyBehavior here. This is something that exists inside of my next 15.1.6. All right. Great. So we have that now.
And I think that we are okay with ending the chapter here. I think we've wrapped up our infinite component and we prettified this look right here. What we have to do next is we have to actually connect to Mux, our video service. We have to find some videos to upload. And then based on that info, we're gonna add some more fields to our schema for the videos right now, because the videos are pretty simple, only title and description actually.
But later on, we're gonna go ahead and give this a thumbnail, we're going to give it a preview URL, we're gonna give it a bunch of useful things, including some mux specific states as well. So that's our next goal. Nevertheless this is definitely starting to get some shape. Great, great job!