In this chapter, we're going to go ahead and connect our previously made filters to the API. We are also going to introduce one more filter and we're going to add sorting, which is going to look something like this, just so you have the UI visual. Let's start by connecting our previously made filters to the API this time. As always, ensure that you are on your master or main branch, and you can do git status to confirm everything is up to date. Go ahead and do bun run dev and we have to add our filters in two places right because we do prefetching and what we prefetch must exactly match what we suspense in the client component.
So I'm gonna go ahead inside of my app folder, app route group home category page. The reason I'm going to go here is so I can access the product list. The product list is inside of modules, products, UI components, product list. And now what I want to do is I want to pass the filters that I add here, like this, into this query options. So what I'm going to do is I'm simply going to extract the filters from use product filters like this.
And then what I will be able to do is just spread the filters. So right now, you can see that adding 1, 000 to the minimum price removes this. So let me just go ahead and remove that filter from the URL. But you can see that it already works. So if I go ahead and add, I don't know, 50, nothing changes.
But if I go 100, still nothing changes, but 101, and they no longer exist for the minimum price, right? But there is an issue with that. The issue is that we are not prefetching this correctly. All right? So at the moment, this is only happening here, but it should also be happening here in the prefetching.
So how do we access those params? Well, we can do that using search params, right? That's one way of doing it. But it would be nice if we could just infer the types from this useProductFilters from NUX. And that's exactly what we are going to do.
So go inside of useProductFilters here, and you're going to modify this. So instead of parse as string coming from NUX, we are going to import this from NUX server, like this. And then we're going to export const params here and I'm literally going to copy these and put them inside like this And then let's just use the params instead of this, like that. So that's now solved. And let's export const load product filters create loader from Nux params.
So now we have a loader, which we should import from Nux server here. And now we can use this in this page right here. So the first thing I'm gonna do is go back here and I will import type search params from NUX and you can go NUX or NUX server it looks like it exists in both of them and now I'm gonna go ahead and add search params here and give it a promise search params, like this. So now I've typed them. And then what I'm going to do is obtain all of them using await load product filters from modules products hooks use product filters.
And I will just pass in search params. And let's go ahead and JSON stringify the filters. This is from RSC. So I have set a minimum price here and I will do a hard refresh. And let's go ahead and check it.
So, looks like it does render. This is from RSC, but it renders null here. But as you can see previously, it renders with the correct value. And I think I experienced this in my original build too, but it didn't cause any issue. It worked correctly in the end.
So just confirm that you can load your values. If you want to, you don't even have to use this. You can simply do promise and then pass in minimum price and this... See, I'm not even sure what this should be, like string or undefined, for example, and maximum price, max price, string, or undefined. You can do it like this as well and then you don't have to use it, but then you're going to have to add minimum price and maximum price like this and await search params like this.
So then if you want, you can add max price and a minimum price like this. If you want to, you can do that, right? If it's simpler for you, you don't have to use load product filters. And changing this to Nuxt server should not really change anything for you. I think this should just work normally now.
Yeah. And let me go ahead and try doing a console log, min price and max price. RSC. Let's do a refresh. Let's scroll down.
Yeah, and you can see that it always somehow gets to the undefined, even though in this example it has it, right? So whichever method you want to use, You can do it like this, or you can use the filters if you don't feel like typing them one by one. And then I'm just gonna go ahead and spread the filters in here, like this. There we go. So this should work just fine.
I believe the errors only appeared because of hot reload. Great. There we go. So now what we have to do next is implement some new filters. But before we do that, I want to improve this clear button.
Let's go inside of product filters component. So right now the clear button, I believe, does absolutely nothing. So let's go ahead and improve that. I'm first going to go ahead and do const onClear. And I will do setFilters.
And I will set the minimum price to this, maximum price to this, like this. And let's just pass the on clear here, like this. So when you click it, it should clear everything. Let me just see the issues here. Yeah, so if it says cannot use a session that has ended, I think that's hot reload happening.
It was in the middle of session, and then I think that we triggered the hot reload. So I'm going to go ahead and just quickly Google to confirm that that's what it is. All right, so again, looks like it's related to MongoDB. But I think it only happens when you hit that hot reload. Probably the payload connection somehow gets interrupted.
But the main point is nothing that should happen in production and nothing that we cause with our code. Great, so we now have a way to reset the filters, but it would be nice to only show the clear button if we actually have some filters. So let's do const as any filters, object, entries, filters, sum. Yeah, let's wrap this in parentheses. E and value.
Like this. And let's just do if type of value is string return if value is not equal to an empty string otherwise if value is not null like this And now we can wrap this button here inside of has any filters. Looks like my key is never defined here. Okay, so can I just do this? Yeah, for now we're not using the key, so it's fine.
There we go. So it won't appear unless I actually type something and then I can clear. Perfect. And I also just want to give this a cursor pointer because I just feel better when I can click on it like this. Great.
Now let's go ahead and let's implement a new collection called tag, right, because we just successfully connected our filters to the API. Now let's go ahead and let's create the tags collection, which is going to be fairly simple. So inside of your collections, go ahead and create tags.ts. You can copy media and paste it inside of the tags, because it's going to be very, very similar. So make sure you import this, rename this to tags.
The slug will be tags. The axis, you can just read it like this, for example. And inside of fields, the name will be the name of the tag and it will be required and it's just going to have a relation to the products, right? So type relationship relation to products and has many will be set to true and upload the remove upload of course. There we go.
You don't need to specify the access in any way, actually. And let me just fix this, It's bothering me. Okay. So after you've defined the tags, make sure you add it inside of your payload config here. So tags, whoops, tags from collections, tags.
And now we're going to go ahead back inside of the products collection. And now we have to add it to the products. So I'm going to go after a category here and I'm going to copy it. Name here will be tags, relationship, relation to tags and has many true. If you have an error here, it means that your types have not yet inferred.
So you can always go and run this to force the types to appear. So now you should have tags, right? So if you go ahead and log in, and go inside of your admin dashboard now. Let's go inside of the dashboard. There we go.
You should now have access to tags. So let's just create a few tags here. So I'm just going to set this tag to be called, I don't know, really don't know. Test, Let's just do foo. Let's go ahead and create another one.
Just a couple of them. Foo, bar. Create at least a few because we're going to test pagination on them. So test. They don't have to be associated with any product for now.
Lorem. And let's do one more. Like this. So now You can go to a product and if you want to, you can give some a tag. And you can see we have an issue here because it's loading them as IDs.
So let's quickly go back inside of our tags here. And let's add admin, use as title, name. When you refresh, you should see the names actually. So for example, I'm going to set the Lauren to one of these products. Great.
So we added the new tax collection and we have added tags to the products. But now we have to create the tags procedure and add infinite load, which is easier than you think. So let's go inside of source modules and let's create tags here. And I'm just going to go ahead and copy server from products and paste it here. So we have the procedures here.
I'm going to rename this to tags router and I'm going to leave get many as it is. But it's going to be much, much simpler than that. So your input here will have a Z object with a cursor, which is Z.number and default 1. And besides that, it will have a limit, which is z.number with a default of 10, like this. And we're going to remove all of this here.
Everything is too complicated for this case. And we can remove these imports from here as well. And instead, what we're going to do is we're going to search for tags. And we're going to add page input cursor and limit input limit. That's it.
That's pagination. That's what we need in the API for the pagination. One thing I want to do is just change the default limit to a constant. So what I'm going to do is I'm just going to go inside of source and create a new file here constants dot ds and export const default limit and set it to 10 like this or maybe eight whatever you want and change this to default limit from constants. There we go.
So now we have the tags router and now we have to go inside of TRPC routers underscore app and we have to add the tags here to tags router. So let's just go ahead and add tags router. There we go. And now we have to go ahead and actually load them. And we already have a couple of things prepared for us, especially inside of this, filters.
So let's go ahead inside of product filters right here. And what we can do is we can easily copy and paste the product filter below and give this one instead of price tags and remove the border bottom zero from the one above. And now you should have tags here, which as of the moment, repeat the exact same thing. So now let's go to use product filters here. And let's go ahead and add tags, which will be parse as array off.
And you can import this from NUX server as well. Ours are as array of ours as string with options clear on default set the true so now we have the tags here as well so inside of our new product filter for tags I'm gonna add tags filter here And we can go ahead and already give it some props, which is going to need so the value in the future will be filters dot tags, which we now have, and on change, we'll get the value and call on change tags and pass in the value. As simple as that. That's the power of this reusable thing we've created here. Now let's go ahead and let's create the tags filter.
In the same place we created the price filter, which is products, UI components. So let's create tags filter dot TS. We're going to start by creating the interface tags filter props like this. And then let's go ahead and let's create the actual component here. So make sure you use the props.
And in here, we're heavily going to rely on the checkbox components. So make sure you have the checkbox component. And let's go ahead and do a div here. Make sure you call this .psx so you have the proper syntax inside. Let's give this a class name of flex, flex column, and gap y of 2, like this.
So what I want to do here is I want to add the RPC use the RPC from the RPC client. And in here, I want to add the data from use infinite query from 10 stack react query. So this is the first time we are doing some infinite querying here. And inside of here, I'm just going to add it here, PC dot bags, dot get many. And now inside of here, we have to pass in the query options.
So I'm going to go ahead and pass the limit. And it's going to be the default limit from constants, like this. But I think that I might need to use infinite query options. There we go. So this, let me see how this is supposed to look like.
So I think that it should have two sets of options like this. One is this and then the second one, if I'm correct, we're going to see now get next page for RAM. That's it. So we get the last page and we simply return last page docs length is larger than zero, then let's get last page, next page or undefined. So it's that easy because we have built-in pagination in payload.
Yes, You might be wondering how come we have the infinite query options. Infinite query options. I don't know if this is still the case. But I think that if I go inside of tags procedures, and if I remove cursor from here, you can see that you can no longer use infinite query options. It doesn't even recommend you to do that.
So you need to have a type of cursor in your procedure input for that procedure to unlock, let's say unlock infinite query options. There we go. Great, so now we have the data. We can also extract isLoading from here, fetch nextPage, as nextPage, and isFetching nextPage. So we have all of these options here now.
Let me just prettify this a bit. There we go. Now let's go ahead and use them. So first thing I'm going to do is I'm going to check if we are loading. So if it's loading, in that case, div and a loader icon from Lucid React with a class name, size four, and animate spin, and a class name flex items center, justify center, and padding four.
And if we are not loading, we're just going to do data?pages.map, get the page, like this. And then we're going to do page docs map and get the tag. And then a div here. The div will have a key of tag.id, a class name of flex items center, justify between and cursor pointer. And we're going to have an onclick here for now an empty arrow function inside a paragraph with the name of the tag and the class name of font medium.
And below that a checkbox component which we have imported. And we're just going to give it a checked of false and unchecked change an empty arrow function like this. And outside of this we're going to check if has next page. And if we have next page we're just going to go ahead and render a native button element, load more. And disabled will be is fetching next page.
On click, will call fetch next page and class name will be underline font medium justify start text start and disabled opacity 50 so now let's go ahead and order this like that. Let's go inside of our products filter. Let's import tags filter. There we go. And now let's implement the proper onChanges here.
So I'm going to do const onClick tag will be string if value includes tag on change value filter tag must not equal tag or an empty array. Else on change, go ahead and simply spread, whoops, value or an empty array and add the tag at the end. I did this incorrectly. So this ends here. There we go.
So basically, if the tag is already in the list, we remove it from the list. Otherwise, we add it to the list. That's what we are doing. So now let's go ahead and let's actually assign these here. So I'm gonna go here and I'm going to do onclick tag.name, tag.name here.
The checked will be if value question mark includes tag.name. And let's do onclick tag.name here. Like this. And let's do on click tag that name here like this and one thing that we should also do instead of tags collections here is that the name is unique So we want to make sure that each tag can only have one unique name. So in case that breaks your app because you have two of the same, I think you can just go inside of payload, inside of tags, and just remove if you have some duplicates here.
But if not, you can always reset and seed your database. So now we can safely use name here as the identifier. So if I go ahead here, you can see how it's loading now. And we have some issues here. I think, again, this is MongoDB.
There we go. So no problems. You can see how nicely it loads them. And if you want to, you can try this. Change the default limit to 2.
And now I think that it should load them. Let me just see the error here. Okay, that's something else. Now you can see it offers you to load more. And let me just go ahead inside of the tags filter here.
I just want to add cursor pointer here. There we go. Load more. And you can see how it loads more until it reaches the end. Perfect.
And when you click here, you can see how it adds them, right? And you can see how they stay selected. And you can clear them. Oh yeah, you can't clear them yet because in the product filters we forgot to reset the tags to an empty array. So when you clear them now, there we go.
Now it works. Great! So I'm just gonna go ahead and remove them from the URL. Yeah, it looks like I have to click twice on the first one to get it on. But OK for now.
And let's bring this back to eight. OK, so what we have to do now is we have to connect this to the API. So let's go back inside of products, procedures, and just like we did for the input min price and maximum price, we now have to do it for the tags. So tags should be a type of array and string inside, and of course nullable and optional. And once you confirm that you have them, we're just going to go ahead after a category, let's add them last here.
We're going to do if input.tags input tags length is larger than zero. In that case, we're going to do where tags.name in input.tags. Just like that. So I remember that I have added to one product a tag, I think. So let me see.
Maybe this one with the category. I think it should have the tag of lorem. There we go. Let me just clear my filters so they are loading. So if I click on lorem, only one appears.
But if I remove, both of them appear. But if I select some other ones, in that case, none of them appear. So I would say that these filters are working as expected. Now just a reminder, If you go inside of your page category, if you manually are writing your filters, remember to now add the tags, right? And remember to add them here in the prefix.
So mine is automatically inferring by using the create loader here. I still think this is the correct way to do it, at least. So I might actually do one change here after reading a bit about server-side usage here. You can see that every time they use Nuxt server, they separate it in a file so they can create... Well, this is an example for cache, right?
But just here above, you can see the example for the loader. So I think we should do the same. Let's go ahead and do this. So instead of hooks here, I'm going to go ahead and create search params.es like this. And I'm going to import these inside.
And what I'm going to do is I'm just going to export const params from here, like this. And then I'm going to export load product filters, like that. And then I'm going to remove load product filters from my use product filters. And now I'm just going to bring these back to my nux like this. So at the moment I'm not going to share between the two even though I think you can share these params.
I think you can do it no problem but I'm just not entirely sure, right? So yeah, I'm going to do it like this for now. I'm not going to share them yet, but I'm going to confirm by the next chapter or maybe in some future chapter if we can do this or not. So I just want to right now make sure that my use product filters has no imports from the server. And then inside of here, my search params, as you can see, has the identical thing, but using Nuxt server and it creates a loader.
So now I have to go back inside of my category page here and I have to import load product filters from products, search params, but I want to rename this. I don't want to use this name. I want to use search dash params. There we go. And you can update the import for this, which is right here, search params.
There we go. So I think nothing should change now. But I feel better having them separated in their own server case. And here I am in my client case. I kind of feel more confident this way that nothing will leak accidentally.
Just a precaution from my side. Possibly unneeded, but a precaution nonetheless. Great, so what I want to do to wrap up this chapter is just add some sorting here because I think it's just very, very easy to do. So let's go inside of our app, app route group home category page. And just before we start this grid, I'm going to add a div here and a class name flex flex column button large flex row on the large items center gap y2 on large gap y zero and justify between And then in here, I'm gonna add a paragraph curated for you with a class name of Text2Excel and font-medium.
And then in here, just another paragraph sorting, just so you can see how it looks like. Let me refresh. There we go. Curated for you and then sorting. I think this is the hydration issue.
That's fine. We're going to resolve that later. So curated for you and sorting. Let's go ahead and let's create the product sort here. So in order to do the product sort, I'm going to add a new hook first.
So let me go ahead and close this source and I'm going to go inside of my modules. Now Maybe I can actually share this with my filters. So let me try doing that. Instead of modules, products, hooks, use product filters, I'm going to go ahead and create the sort values here. So const sort values are going to be...
Let's do newest and oldest and default as const. Like this. You can, of course, use whatever values you want. And now let's go ahead and add sort vars as string literal sort values with default curated, newest, or default. I don't know.
I mean, if you want to, you can do it like this. Let's do it like this. So curated. So it looks better. Curated, trending, and hot and new.
Like this. And let's pass curated by default. So that's for sort here. And now I immediately want to go ahead and do that in the search params here so I don't forget it. So I'm just going to copy it here and parse as string literal here from Nuxt server.
There we go. So just make sure you have the same thing in your use product filters and your search params like that. Now let's go inside of our modules and inside of our products here. UI components and let's create product sort TSX like this. Let's export const product sort.
Let's go ahead and get the filters, set filters from use product filters like this. Let's go ahead and let's return a div with a class name flex, items center and gap of two. Let's use a button from components UI button like this. And let's give it a size of small and then a class name using CNLibUtils rounded full background white hover background white so we override any effects and then if filters dot sort is not curated let me just see filters dot sort is not equal to curated. In that case, we're going to render background transparent, border transparent, hover border border, and hover BG transparent, like this.
Now let's go ahead and give it a variant of secondary, on click, set filters, sort, curated. And let's add a text here, curated. Now let's go ahead and copy and paste this and replace the curated value with trending and trending here. Let's copy it one more time. And this one can be hot and new.
And hot and new, like this. There we go. So now let's go ahead and just mark this as use client here. Let's go ahead back inside of our page with the category and let's go ahead and add the product sort component which is a self-closing tag imported from the modules. So when you refresh, hopefully you should have a nice-looking picker here, and you can see how you can change, And you should be able to see the values change in the top corner as well.
You can see Sort is trending, but when you click on Curated, it should clear it completely. Great. And I also want these values of mine to clear so here's what I'm gonna do I'm gonna go ahead inside of my use product filters here and I'm gonna add with default here to be an empty string and I'm going to copy it for the maximum price as well and here I'm gonna give it with default like this So we can copy this and go inside of search params and just paste it here because we use the equivalent imports but from the server. And I think that now you will have a much better experience because if I type something, let me try, I will remove. So this is my current URL.
You can see how it looks weird because I have these empty ones here. So now if I go ahead and add 500, it looks like this. But if I remove 500, my URL now looks like this, which is what I expected. So now I'm going to try and just add some filters. And if I click Clear, it removes all of them.
Excellent. And you can see our filters here are working as well. But we just have to connect them to the API. But before we can do that, you can see that now the clear is always active, because in the product filters, has any filters doesn't take care of that. So let's go ahead and extract the key from here.
And let's do if e is equal sort, we can return false. Let me refresh. Okay, maybe not. This should be okay. And let's do...
Let's also do this. If array is array, return value length. So this is for tags. So you need to have all of these for this to properly work. So now sort will not cause this to show.
And you can properly wait until these are selected for this to be cleared. Great. And now you can go ahead and connect it to the API. So what I'm going to do is I'm going to go inside of search params. And I will export sort values from here, simply because this is my server side, search-params.
And then I'm going to go inside of products, procedures. Basically, I'm going into my get many where we do all the filtering. And now I'm gonna add sort z.enum, sort values. And I will import them from search params because I'm certain this is what I use for the server, right? And now I'm just going to add nullable and optional here as well.
And now we're going to go ahead and do the sorting for this as well. So what I'm going to do is let sort to be a type of sort from payloads to make sure you import that that type. And by default, I'm going to set it to minus created at. And then if input sort is equal to trending, for example, you can go ahead and do whatever you want here. So I'm just going to leave them to be all minus created at, and then I'm going to revisit this later, right?
And let me just go ahead and do trending. So if you want to, you can reverse some of these. If you click on trending, try adding a plus here. So at least it goes in the different direction. And then for trending, you can sort by name and make sure that you add the sort to the final here.
So now you can try, you know, let's see. So if I click on trending, you can see subcategory is first and category is last. And then if I click on hot and new, it gets reversed. You can see, right? So basically, that's how sorting works.
And as you can see, it works quite easy. And of course, you can improve this. So since, yeah, this should be curated. Since this is my default, I'm going to leave that to be by newest created. And then in here, we can, for example, do name or maybe plus created at.
Right, so you can, I'm gonna come back to this and maybe give you some better options, but I just wanna give you an idea of how this works. Great. That's it. We've added sorting UI, and we connected sorting to the API. Let's go ahead and push all of these amazing changes that we did.
And I think that we don't have to modify anything in here because we entirely rely on load product filters for the prefetch here, right? So in here, it matches exactly. And inside of my product list here, I also don't have to worry because this matches as well. I'm going to explore if we can reuse this too because it's an exact copy right now, but for now I'm satisfied. So This is 13 API filters and sorting.
So let's do git checkout b 13 API filters sorting. Git add, git commit, 13 API filters and sorting. Git push uorigin 13 API filters and sorting. You should now notice that you are on a new branch so let's go inside of our github here. Let's open a pull request And let's see what our reviewer has to say.
So I'm going to go ahead and merge this pull request. And then I'm going to go ahead and ensure that I pull my changes back into master. Let's go ahead and do git status like this first and then git checkout master or main and then git pull origin master like this. So all of those changes are ready. And let's do git status.
There we go. So you can now check out the graph. We detached to check out the API filters and sorting, and we merged the pull request back. And I forgot to give you the overview by CodeRabbit here. I immediately merged it so I thought that it would not trigger but it still did it after I merged it, right?
But it did caution me that the pull request is closed. But as you can see, we have enhanced product filtering and sorting options with new tag-based filters and sorting criteria, which is exactly what we did. As always, step-by-step change summary here and some more advanced sequence diagrams, even the ones for use infinite query hook to load new tags inside of our product filter right here. There we go. I believe that is it.
So I have already merged my branch. You can go ahead and do so. I'm satisfied with what we've got here. And now let's go ahead and just mark this as complete. There we go!
Amazing job and see you in the next chapter!