In this chapter, the goal is to create video categories. This is the component which we are going to create. And we are going to put it right here, below the search page, and just above where our videos are going to be. And this is what it's going to look like. We're going to use the carousel component from Shazzy and UI, so it's going to look pretty smooth.
We're going to start by creating the categories schema. After that, we're going to push those changes to our database. Then we're going to create a script to help us seed the categories because categories are not something that users are going to create. It's more of a super admin thing that will simply be set in the project for users to then choose from. And then we're going to utilize our knowledge from chapter 6 about prefetching data.
In this case, prefetching categories in a server component. And then we're going to use SuspenseQueryDem inside of a client component. So we're finally going to leverage that knowledge in a real world example. And this is a fairly simple, this is a very simple entity for us to do because there aren't really any create, read, update methods on here. And one more thing that we are going to do here before we prefetch is organize the RPC routers, right?
That's one more thing we're going to do. All right, let's start by creating our seed script. So I'm gonna go inside of the source folder and I will create a new scripts folder inside. And inside of here I'm going to add seed categories.ts. What I'm going to do now is I'm going to add a to do here, Create a script to seed categories.
So let's just have this ready. We can't do it yet because first we have to go inside of database schema.ts. So Let's go ahead and export const categories to the pg table. Categories. We're going to have the primary ID to be UUID.
So let's add that. Each category will have a name which will be required and also unique. So no two categories will have the same name. Now the description, well, it will depend on how you want to handle these categories. As you can see, you know, in here, the descriptions are really not going to be used for anything, but they might be useful for you if you ever decide to create a dedicated screen for each category.
So then you can have a little description saying, you know, for comedy, you can have a unique thing saying like the funniest videos or something like that. So if you want to you can add an optional description here like that. So not null. So it's just an optional description. And let's just copy our two created at and updated methods here.
And one thing that I'm going to do here is I'm going to add an index for the name of the category in case we want to query by the name. So I'm going to get t and I will return unique index here, name index and I will do on t.name. There we go. So now we have categories here perfect, so what we have to do now is we have to go inside of our terminal and we have to run bunnix drizzle get push like this and once you run this you should get a well a success message that everything was pushed fine and Then we're simply going to check our Drizzle Studio to confirm that this schema has truly been modified. So it says no changes detected.
Oh, my apologies. I am in an incorrect project. Let me go ahead and go back here. Ignore that for you, everything probably went well. What happened to me was I was in a different terminal.
So new tube project, Bonnex, DrizzleKit push, I will just run it again. And now I expect everything to work fine. There we go. So changes applied, pulling schema from the database. I noticed something was wrong and that's what it was.
So now let's go ahead and do bannocks-drizzlekit-studio and let's go to local.drizzle.studio just to confirm that we now have categories in here with ID name description created at and updated at great now we can go back inside of our seed categories right here. And let's go ahead and define our categories. So const category names are going to be the following. Cars and vehicles, comedy, and well, it makes no sense for you to watch me type this out. I'm just going to copy all of the categories which I'm going to have, and you can pause the screen and add them if you wish.
It really doesn't matter, right? The more you add them, the better the site is going to look like. But these are the ones I was able to found in the YouTube Studio category select input. So it only allowed this categories right here. So I decided to only add them as well.
Great, now let's do an asynchronous function main. Let's add a console log here, seeding categories like this. Now let's open a try and catch block. Let's resolve the catch block first. We're going to add an error here, error seeding categories.
Like this, now let's add a little space here. I have a typo here. And of course, let's do process.exit1 because we will be running this in a script. And make sure that you execute the main method here outside of itself of course. And in the try method what we are going to do is we are going to do category names.mat and get the individual name and return an object here.
And we're going to reuse the name and we're going to generate the description by saying videos related to and then just name to lowercase. Like this. So basically we're just gonna create a very generic description for every single category name. So it's gonna say, you know, videos related to sports, videos related to travel and events. So we are creating the values and what we have to do now is await database which you will import from s slash database dot insert into categories which we now have from our schema as well and pass in the values which are values like this and after that console.log categories seeded successfully like this.
Now in order to run this script you are going to have to use something that supports this kind of imports, right? That's why in the beginning of our tutorial, I told you that I highly prefer if you used bun for this, because bun lets us easily run TypeScript scripts with ES6 imports. So I'm going to show you first how it looks like in bun. So let me go ahead and try running this. Let's go inside of the terminal here and I will do bun run, actually not run, just bun, scripts, actually source, scripts, seed categories.
There we go. Seeding categories and then categories with my typos seeded successfully. So if I now go ahead and open the Drizzle Kit Studio again. And if I refresh the entire thing now, you should see 14 categories here. People and blogs, film animation, everything here.
And you see that you have the relevant description as well, right? Now, I'm not sure exactly what happens if you try to do NPM, my apologies, not NPM. If you try to do node, source, scripts, seed categories, there we go, you get errors, right? Because we are using import, which is not something that you can use. I'm not even sure what's the error.
Can I use import statement outside a module, right? It's just things like this that make me highly prefer using bun in these tutorials. If you really, really want to use Node, you can do it by adding const this to be required. And honestly, I'm not even sure if this can use an alias. Let me try this.
I'm not even sure how this works. This is so funny. I have not used them in such a long time. I'm going to pause the video and try and give you a solution if you really want to use a node. So I think that the solution might be to use requires like this.
I think so I have to escape this and go inside. No, or maybe just here. See this is why I really really just prefer using BUN. Let's just try. If I run node, cannot find module.
OK. Yeah, I give up. I'm really not gonna try and solve this because BUN just makes it so much easier. So please just use BUN for this. It's gonna be ten times easier for you to continue with the tutorial.
Alright, So now that we have our categories set, what we're going to do is we're going to create our categories procedures, right? So let's go ahead and do this. Let's go inside of source, inside of modules, and let's create a new folder called categories like this. And inside, let's create a server folder and then a procedures.ts like this. And inside of here, what we're going to do is we're going to import database from database.
We're going to import categories from the database schema, and we're going to import base procedure and create trpc router from trpc init. And then we can safely export const categories router to be create trpc router and passing get many and this will use a base procedure meaning it's going to be public to everyone. Inside of this query we are going to have an asynchronous method And all we're going to do is we're going to get the data using 08 database select from categories. Like this, and we return back the data. That's it.
That's our categories router. One issue though is that we are not really using it anywhere so let's go ahead and let's refactor our TRPC routers here. So go inside of TRPC routers And what you can do now is you can remove the hello protected procedure. You can remove this from here. You can remove Zod as well.
So you are only going to need to create the RPC router. And above you're going to import categories router from modules categories server procedures and then passing categories here to the categories router like this. There we go. Now obviously we're going to have some errors instead of source app folder home and instead of here we have page right Hello no longer exists. So how about we modify this to load our new categories?
So let's prefetch the categories. Let's see, categories, my apologies, categories get many, prefetch like this. And let's go inside of client here and let's do the same thing. Categories get many and nothing is being passed here. And what we are going to do here is simply JSON stringify data like this.
So if you now go here and refresh and of course have your app running, bun run the all and reload your application, you should now see the categories inside of a JSON format right here. There we go. Great! So now what we have to do, I believe, is inside of our chapter 8. So let's keep track.
We have successfully created the categories schema, we pushed the changes to the database, we have seeded the categories and we have organized our TRPC routers. And now we have to properly prefetch them, but not in their proper place. So now we have to create the categories component. So let's go ahead and fully focus on this now. We are not going to be needing this protected anymore.
We just use this for demonstration. Let's remove that. We are also not going to need the client.dsx anymore. Let's remove that. And let's exclusively focus on home page.dsx.
Inside of here, Let's remove the page client and let's also remove the suspenses and the error boundaries as well. We are only going to do void trpc categories getmany.prefetch and here's an important thing. When Next.js or Vercel tries to build this part of our app, it will think that this is a static application because it has no idea that this is actually prefetching something. So what you have to do, And don't worry, you're only gonna have to do this in page.tsx. And we're going to keep track of every single page.tsx in our app, which does prefetching.
We're gonna be careful. What you have to do is export const dynamic to be force dynamic, like this. Otherwise you're going to have build errors if you don't do that in places where you prefetch. And now inside of here, what we are going to do is we are very simply going to pass the home view. Now we don't have the home view yet, but we are going to create it.
And I'm also going to kind of explain to you the places where I prefetch and the places where I call the use suspense query. Because I will make some decisions about where I will do that and where I will absolutely not do that. And also to answer, you know, where did the suspense and the error boundary go? We are also going to take care of that. But let's finish doing this.
How about we don't call this home. Instead, we just call it a page. I prefer using arrow functions. Export default. Don't forget the default export when it comes to pages.
Let's create an interface page props here and we're using search params here which means we have a promise like this and an optional category id. So this is not required to exist only if the user selected the category ID will it exist. And let's go ahead and destructure that here. Page props, search params. So not params but search params like this.
And then inside of here, you can destructure the category ID from await search params. And in order to use await, we also have to ensure that this component is asynchronous. And then we can go ahead and pass in the category id. There we go. So we are leveraging the server component to await our search params and we are also leveraging it to fetch all the categories, to pre-fetch the categories.
And now we're going to create a new module here. Actually, we already have it called home. We have the home layout and we have the components here. So now what we are going to add is another type of UI components called views. And inside of the view we are going to go ahead and we are going to create a homeview.dsx.
The home view component will have an interface home view props which will accept the category ID which will be already destructured here from the promise so no need to do that. Let's export const home view here passing the category id from home view props and inside of here what we are going to do is we are going to return a div and we are going to give this div a class name of maximum width of 2400 pixels. I'm going to explain why we are doing this in a moment. Let's add mx-auto, margin-bottom of 10, ex of 4, padding-top of 2.5, flex, flex-column and gap-y 6. Like this basically what we are doing is the following I think if I add border and background red 500 it might be a bit more clearer so let me just go ahead and do this and let me go use the home view inside of my actual app folder home page so I'm going to import the home view from modules views home view I will refresh here and there we go.
You can see my home view now, but you can see that at one point it will stop expanding its width, right? So if someone is looking at this at an ultra-wide screen, we are not going to keep expanding the videos infinitely, right? We're just going to limit to how wide we are going to support our category carousel and our video grid. So that's what this does. It limits to how wide we are going to support that.
So we can now remove the background color red and the border. We don't need any of those. And now instead of the home view, we are still not fetching anything. Instead, what we're going to do is we're going to render the categories section and pass in the category ID to be category ID, like this. So the last type of component for me to introduce to you are the sections like this.
And inside of here, simply create category section.dsx and the section will finally be an actual client component. I will just copy the interface from home view props because it's exactly the same. And I will simply rename it to category section props. Let's export const category section. My apologies, not category section.
Categories, multiple. I want to have proper names. Like this categories. And inside of Here, what we are going to do is we are going to fetch our categories like this. So categories, PRPC, let's import from client.
Make sure you're importing from client here, but in page you have to import from server, right? So in the client here we are going to access the cache which we have, my apologies, getMany, getSuspense, useSuspenseQuery. We are now immediately going to access the cache which we have thanks to this prefetch in our server component here. So now that we have that let's go ahead and return a div json stringify categories. So the same thing we previously did but this time in a structure that we are going to follow throughout our project.
So let's go ahead and add this and the structure, the category ID. Alright, and let's go ahead and import the categories section from ./.sections, category section. Great! So, now if you go ahead and refresh, nothing much should change. Everything is pretty much still the same, but this is my general UI structure.
So instead of my app router, I'm not going to do any UI elements. As you can see, I will immediately import the home layout from the Modulus Home. For the page, I will always import a single home view, but I will hydrate the client here. The reason I'm choosing to hydrate the client here and not inside of the home view is because I don't want to forget it, right? I don't want to accidentally forget that I have to hydrate this client.
So wherever I am doing a prefetch is where I'm going to add hydrate client. It's this kind of consistency that gives me a peace of mind that I didn't forget to do any of those, you know, if I prefetch I have to hydrate and I also have to use the proper use suspense query. This is giving me a peace of mind that I will always do the same thing every single time. Great! So that's what my pages are going to be for.
My server components will be used to prefetch things, to hydrate the client and to render its respective home view. Now the home view component itself will be purely decorational. All it's going to do is going to split the screen into sections and then the section will finally be a client component which is going to leverage the prefetching from my server component. That's how that is going to work. The reason I am separating them into sections here is because each of my sections will have their own suspense and error boundaries, which means that I'm going to have categories sections here, but below I will have videos section.
So both of these will be able to load independently of one another. And if one crashes, it's going to have a nice encapsulation as opposed to my entire view crashing. That's what I'm trying to achieve here. I'm trying to find the balance between too granular of suspending and also too grandiose of suspending as, for example, suspending the entire home view. I'm rather going to go a bit deeper into sections.
I think this gives quite a perfect balance between not too granular, but not too grand either. All right, so we now have the categories here, but something that we don't have at the moment is any loading screens. So you can choose how you want to do this. So you might think, okay, so I have to wrap this inside of, you know, suspense, right? Because my categories section is using use suspense query.
We've learned that we have to wrap those inside. But I'm actually not going to do that. Now, I'm not sure how good of a practice this is, but I really like it this way. This is what I'm going to do instead. I'm not going to export this, and I'm going to rename this to Category Section Suspense.
And then in here, I'm going to export from categories section like this. And I'm gonna explain why I chose this. It's pretty much the same reason why I chose to use hydrate client in the place where I'm doing prefetch. I'm choosing to have inside of the same component, I'm going to have the suspense and the error boundaries. So I never forget that I have to wrap whatever, whenever I'm using useSuspenseQuery, I never want to forget about wrapping them in these two.
So that's why I simply decided, okay, every single place where I use, use suspense something, I'm going to do the actual suspense and error wrapping inside of the very same component. And that way I will never forget about it. Right, so that's my solution of doing it. If you have any better practices feel free to introduce them but keep in mind that I will be using this throughout the entire tutorial. A react error boundary like this and let's go ahead and add some fallbacks here.
So add a paragraph loading like this and a fallback here, error like this. And then simply render the categories section suspense. But we also have to pass in the props. So let's just copy this. Yeah, this is the one annoying thing we have to do.
We're gonna have to do double prop passing here. Yes. But other than that, it's pretty much okay. All right, so now we have the categories section here and nothing has changed here because what we're importing here is this, the suspense with the error boundary. That's why I'm only exporting this and I have renamed this one to be category section suspense.
So now you can see that I have a proper loading suspense happening here. And if for whatever reason inside of my categories router here, I were to throw a new TRPC error here with code bad request, I would get the respective error boundary. So it's loading, it's still loading, so only after a couple of retries will it fall back to my error boundary. There we go, error boundary activated. Great, So I'm now going to remove this error here and I'm going to go ahead and what we're going to do now is finally develop this nice UI right here.
So let's go ahead and do that. The component that will be responsible for this will be called a Filper Carousel and it's not necessarily going to be used just for categories. It can be used for something else. For that reason I will not put it inside of any module, I will just put it inside of my components. But I'm not going to put it inside of the UI folder purely for the reason that chat-cn uses this folder.
So I will reserve the UI folder for whatever chat-cn command line interface decides to put inside. But I will use the components folder directly like this outside of the UI and I will call this filter-carousel.tsx. So as you can see filter carousel is inside of components but outside of my UI folder. This will be a client component and let's go ahead and do the following. Let's first of all create an interface here.
I'm just going to copy it so you don't have to see me typing this thing out. So go ahead and pause the screen. The value can be optional string or null. Is loading can be optional. On select will choose a specific value and the data has to be in this kind of format value or label like this and the data actually has to be required right actually yeah let's make the data always be required because we do allow an empty array so that makes sense great let's export const filter carousel here We now have to assign the props like that and inside of here we can now the structure value on select data and is loading like that.
Now, what we have to do is we have to use the carousel elements from Shazam UI. So let's add all the imports necessary for that. So that's the carousel, Carousel API, Carousel Content, Carousel Item, Carousel Next, and Carousel Previews, all coming from components UI Carousel, double check that you have that component. Great, so now that you have this, we can go ahead and just build a basic look of this. So let's go ahead and do the following.
I'm going to go ahead and return a div with the class name, relative, and the full width. And now what I'm going to do is I'm going to add the carousel like this, and I'm going to go ahead and give this an options of align start and I'm going to give it a drag free to be true. I will also give this a class name of full width and px as well. Like this. I'm then going to add carousel content here and I'm going to give this a class name of minus ml minus three.
If you're wondering why minus ml minus three, That is because this is how you add certain padding between the items. It's a trick from the chat.cnui documentation. If you want to, you can have it opened. But yeah, the chat.cn in the background for the carousel I believe uses mblock carousel react and basically that's how you add padding between items in each carousel here. So what we are going to do is the following.
How about we just add one carousel item here, which will simply use our batch component from .slash UI badge or slash components UI badge, however you want to import it, because we are in the components folder. So we are much closer to the UI folder than usual. So whichever one you prefer. And we're simply going to write all inside like this. And I'm going to pause here so that I can go back inside of my source modules here, inside of home, sections, categories section.
And inside of here all I'm going to do is I'm just going to return filter carousel from components filter carousel and I'm going to pass in the value to be category ID and the data to be data. My apologies. Yeah, we can call it data, we can call it categories, however you prefer, Let's call it categories. Yeah, the issue is that we actually have to give it a specific type because data expects a value and a label, right? So let's go over our categories, get the individual, is it name?
Like this, and ID, and return an immediate object with value to be category, actually just ID, and label to be name, like this. And now you can pass in data. There we go. Or you could have also, you know, passed this as category and then you could have used category.id and .name, whichever one you prefer. Let me refresh now.
And there we go. You can see a small old badge right here. So I just wanted us to render this component so that when we develop it, we can see the changes. So obviously it doesn't really look perfect yet, but we are going to modify it. Let's go ahead and let's give this badge which renders the all text a dynamic variant.
If the value is currently null, we're going to use default. Otherwise, we're going to use secondary. And let's also give it a class name of rounded large, px of 3, py of 1, cursor of pointer, white space, no wrap and text small. So we're going to leverage the fact that if no value has been passed and we are just going to assume that that means that the badge all is selected right. Great so now as you can see this is what that looks like.
So Now let's go ahead and do the following. Let's go ahead and while we are inside of the carousel content, I want to check if we are not loading and if we have data. Actually, if we are not loading, in that case just iterate over the data like this and render the carousel item. And inside of here, render the badge. Let's go ahead and give each carousel item a key of value and inside of each badge item dot label.
So now if I refresh, looks like I'm still not getting anything here. Let's see, instead of my categories section, let me check what is my data here. Am I getting all the data necessary here? It looks like I am. This seems to be my data.
So we definitely have this, but for some reason it seems to not be loading. So is loading. All right so if not is loading and and data.map. Am I using something wrong here? If I just go like this will that work?
No it still acts as if my data does not exist. So maybe it's not a problem in... It's not... Yeah, the data is available here as well. So perhaps I'm using the UI incorrectly.
Inside of my carousel content we are rendering carousel items here and the badge here. Let's see. Could it be that I'm simply missing the classes? So class name pl3-basis-auto. Is it that?
It's not that. Very interesting. Okay. So pl3-basis-auto, Carousel item, badge, item label, all of these things should work, but for some reason, they don't appear to be working. All right, so I was debugging a bit using inspect element, And I did find the rest of our items.
They're right here. They're pushed all the way to this other side, it seems. So let's go ahead. We're obviously doing something incorrectly here. I think that for this carousel item I'm missing the same class name here.
PL3BasisAlto. There we go. So I was missing that class name which accidentally seemed to have pushed all the elements in this corner here. Great! So we can now see all of our categories and now let's go ahead and give some additional styles here.
So the badge will do the same thing. It will have a dynamic variant. If value is equal to item.value we're going to use default, otherwise we're going to use secondary like this. And let's give the badge itself a class name, roundedLG, basically the exact same thing as above. So let's just use this.
There we go. There we go. Now these are looking much better and you can see how we can drag them but there are a couple of things I don't like. The first thing is I'd like to have some arrows. The second thing is I don't like the cutoff here.
So let's go ahead and resolve that as well. We can do that quite easily by going outside of the carousel content and simply add carousel previous and carousel next. So simply add them, give this one a class name. So for the previous give it a class name of left zero and Z index of 20. And this one, right zero and Z index of 20.
And there we go. You now have nice little arrows that you can use as well. One thing that's missing are the cutoffs. So I really don't like how the cutoff is looking like. So I found a very simple solution thanks to AI, which is just to create like some fades.
So let me remove the console log here. Inside of a relative pool, I'm just going to add a comment left fade so you know what this is for. And this will be a self-closing div. And all it's going to do is have a class name. But we're going to have it have dynamic classes.
So for that, we're going to utilize our lib utils, which we got when we installed the chat CNUI. I don't think we've used it at any point so far, but what this is good at is making dynamic Tailwind classes. Yes, you could just use normal JavaScript here, but trust me, this makes it way easier, especially when you're about to have a bunch of classes, a bunch of options, a bunch of variants, right? You will eventually give in and start using this. And most importantly, it will properly merge Tailwind classes.
So you might think you're doing something in a simple JavaScript way and then your Tailwind classes are not going to behave as you expect. So it's always safer to use this in my opinion when you need dynamic Tailwind classes. The way it works is very simple. It accepts an infinite amount of params And you can write them either statically like this, absolute left 12, top zero, bottom zero, width of 12, Z index of 10, background gradient to right, ROM white to transparent, pointer events none, and then add a comma. And again, you can continue background red, blah, blah, blah, or you can make a dynamic one and well we don't really have basically I want to make something here So this is what I'm going to do.
If false, hidden. This false will be a variable in a moment. We don't have it yet. But now, you can see how we have a nice fade here. But there is an issue is that I don't want this to be hidden already, right?
I want it to be active in this case but I don't want it to be active in the beginning because it's hiding my first element here. But basically this is what we want to do. We have a left fade here. Let's go to the end of our div here and let's add right fade and instead of left 12 this will be right 12 like this and this will be a gradient to L like this. And this will also be false.
And now you're going to have a nice little fade at the both of these things here. So what we have to do now is we have to enable, well, let's go ahead and call this proper API for the carousel, right? Because when something is clicked, I want that to be sent to my, well, to the outer component, right? And from Shazian documentation, what you can do is you can enable Carousel API and expose it, and then you're going to have the ability to programmatically control this carousel, which is quite useful. So let's go ahead and import use state from React here.
And let's pass in a type of carousel API. Do we already have it imported? We do, great. So carousel API here, The current value will be zero as in the starting position. And we're going to have the count of total items.
Let's add useEffect. I'm not a big fan of using useEffect ever, but sometimes you just have to. So make sure you've imported useEffect from React here. If we did not load the API for the carousel, there's nothing we can do. So we can just break this method.
Otherwise, let's call setCount API.scrollSnapList.length, execute it and then call the length. And let's do setCurrent to be API selected scrollSnap plus one. API on select, set current, API selected, scroll, snap, plus one. So now we have total control over how many items we have and what is the current position. And then we can properly hide this fade if we are on the first element or the last element.
So yeah, it's a bit of an effort, but it makes the project look much better. It's the details that matter. Let's go inside of the carousel here and let's add set API to set API. And I think that's all we have to do for this. And then instead of false we can check if current is equal to one.
In that case hide this. So now you can see no fade in the beginning, but when I move it starts to fade. So it doesn't have the ugly cutoff. Perfect. And for the last one, instead of a hard-coded false here, we're going to do if current is equal to count, right?
So if we reach the end of our line, there we go. No fade here, but if we are not, it's gonna show it like there are more things here. Great, so we have our basic carousel here. So what I'm wondering now is this. This shouldn't be if value is equal to null.
This should just be if value does not exist and then all will be selected by default. That's why it wasn't selected because it wasn't explicitly null. All right, so we have that. And we can now send our data to this filter carousel. But what's missing is a nice loading state.
So how about we leverage this filter carousel and give it a prop which we already have is loading and let's go ahead and now kind of make this a nicer experience. So we are only going to render the data if we are not loading. So if exclamation point is loading only then render the data. Otherwise we're going to go ahead and do the following. If is loading, in that case, you're going to render an array, actually.
So yeah, don't return any parentheses. Just collapse the element like this. We're going to create an array from 14 elements because that's how much we have in the database currently. So skip the first argument and just get the index. And then we're going to mock the carousel items here.
So give this a key of index, give it a class name of pl3 and basis auto. And inside of here, We're going to use the skeleton from .slash UISkeleton or from components UISkeleton, whichever one you prefer. This also came in our chat-cn add all CLI command. We're going to give this skeleton a key, actually it does not need a key, my apologies. We're going to give it a class name of RoundedLargePx3PA1 HeightFull, TextSmall, Width of 100 pixels and Font is semi-bold.
And inside we're simply going to add a Unicode for a white space. So it populates the font, so it puts the skeleton in the right height and width and everything, but it will not actually show anything, so it's still going to appear as a skeleton. And if we are loading, we also don't want to show this default all badge. So only if we are not loading, are we going to show this. All right, like this.
There we go. And let's also now go inside of category section and let's add is loading value here and this is how our skeleton is looking right pretty good right but don't worry we are just adding it here now we will add it to the suspense but before we do that, let's actually go ahead inside and how about, yeah, I mean, I think that we should enable, we should make the value required as well. No, No, my apologies, the value does not need to exist always. But onSelect should probably always exist. What's the point of the carousel if you can't select an item, right?
The value can be reset if we click on the first element here, which is the all. So let's do that. On click, on select, null, like this. So we are basically resetting the entire thing. Otherwise, if you click on this proper one, in that case, what we're going to do is we're going to call on select question mark because it can be actually no it has to exist right so we can just execute it item dot value let me just collapse this items here Some of you have asked me why don't I just use Prettier for this.
I use Prettier when I develop the project but I don't use it when I record the project because Prettier can sometimes change the stuff that's not recorded. Like I run a formatter now and it changes some code here. And then you're wondering why our code looks different. So that's why I don't like using Prettier while I'm recording a tutorial. But yeah, feel free to use Prettier.
I do use Prettier, I do use formatters and autosave and all of those things but not when I'm recording a tutorial. All right, so just make sure you have added on the real carousal iteration on click select item value and also yeah no need for this because on select will always exist. Great, so now on select is missing so how about we add onSelect here x onZeroLog x Like this Let's try it out when I click here There we go, you can see each of my elements And in order to wrap up this chapter we have to actually assign this category to our URL to the search params so that it can be properly passed back here and show that it is selected. Before we do that let's actually add a proper suspense here. So what we are going to do is we are just going to use the filter carousel here.
And we're going to give it a loading prop like this. Oh, so it's missing onSelect. Okay. Yeah, let's give it data and empty array and on select an empty arrow function. Not perfect, I know, but I think it gets the job done.
And now we have a much, much smoother loading experience here, right? So you can see how this was my decision, basically. Whenever I'm using suspense query, just a component above it, I'm going to handle the suspense and the error boundary so I can decide how they're going to look like and not so that I have to go back to my view here and then see, oh, okay, so I have to change the suspense. I have to change the error boundary to some other component. No, I'm just going to have it all controlled inside of here.
One step further, for example, could be categories skeleton simply to be explicit. I mean, I know this is funny now, this doesn't make any sense, but you know, if you want to be explicit, have categories skeleton, you can do it like this. So just passing the categories skeleton. It's the exact same thing, don't export it, No need for it to be exported. But yeah, I mean, it gives you a bit of a larger control if in the future you want to add some more structure around the loading element.
I think this looks very good. Let's wrap it up by actually adding the OnSelect option here. So I'm going to add const on select value string null like this. I'm going to parse the current URL using window location href. If we have the value, I will do URL search params.set category ID to the value.
Otherwise, I will delete them from the search params. Make sure you don't add any typos for the category ID here. And then I'm just gonna go ahead and add my router from Next navigation and I will do router.push URL like this and let's see URL to string And let's go ahead and pass in the onSelect here. So now what should happen, when I select here, you can see that my URL has changed to category ID and included the URL that I have clicked on. Now there is a way to make this even faster because router.push is not exactly the fastest way of doing things because it will not prefetch anything.
So if you want to, you can use router.prefetch as well. But perhaps this is something I will explore in maybe some other chapter. I will explore if maybe this filter carousel can wrap the badge element inside of a link component because we can add the query elements, the search params to the link component as well. The link component will be much faster because it will prefetch by default. That's something to see.
Maybe I will come back and do that, But at that point, we're almost certainly gonna have to remove the filter carousel from being a reusable component and use it exclusively for categories. So I'm gonna confirm with my project, with my completed project, if that's the case, then yes, I think we have room to make that optimization, to make this even faster. Right now, you probably have no much opinion if this is fast or not, but keep in mind that later on, every time you switch this, it's going to query the database for a specific category and their videos. So then you might start to feel this being a little bit slower even with our prefetching and everything. I'm gonna see.
Yeah. Basically this should all work for you. You should be able to change this URL and it should reflect the carousel here. In case it's not, here's how you should debug this. You should start from source page right here where you have the category ID.
Make sure you didn't accidentally misspell the category ID. Make sure you didn't misspell it when passing to the home view. Make sure you did not misspell it here and make sure you did not misspell anywhere here. So those are the only ways it can happen, right? Great!
So I think that makes us properly prefetching the categories and we created the categories component. We can end this chapter. Great, great job.