Why Pagination Matters0:00
Let's talk pagination. I think you're going to really enjoy this lesson. So remember how when we implemented the search filtering on the server side, I was saying that client side search is really cool and snappy, but it doesn't really scale because you have to have all the records, all the puppies loaded on the browser to be able to filter. Well, we have now implemented server side filtering, but we are still loading all the puppies to the front end.
Server-Side Paginate Setup0:37
Well, we have now implemented server side filtering, but we are still loading all the puppies to the front end until we start searching. So pagination is going to solve this problem by only ever loading 6, 12, 18, whatever amount of puppies per page we decide. So let's start the setup on the server side in our controller index route. To be honest, this part is going to feel almost too easy. All I have to do instead of getting the result is
To be honest, this part is going to feel almost too easy. All I have to do instead of getting the result is to paginate them. That is it. And just like that, we've got pagination. Alright, thanks for watching. I'm joking. We are now going to implement the front end, but that's all we need to do on the server side. Okay, so if we refresh the app, we are going to have the blank screen of death and the console is going to tell us.
Handling Paginated Response Shape1:20
to have the blank screen of death and the console is going to tell us that something is not right because puppies.map is not a function which you can understand as puppies is not an array. So you cannot dot map over it. So let's go in our IndexPage component where we receive these puppies and instead of returning all of this, I will comment that out and inside we're just going to have a pre tag that
and instead of returning all of this, I will comment that out and inside we're just going to have a pre tag that holds the puppies data. Alright? And here lies the problem. We are back to wrapping our puppies in a data key. And the reason pagination has done that is because it wants to separate the actual data from the pagination data. You can see we have links here, we have meta informations about the current
Generating Pagination Types2:00
You can see we have links here, we have meta information about the current page and the links. And so all that meta information here is going to be useful for us to implement pagination buttons for previous, next, and so on. Now, instead of flying blind in the front end, because we don't have any type signature for the paginated results, we are going to just generate a few types.
for the paginated results, we are going to just generate a few types that gives us more visibility on what should be accessible from that paginated results. Okay? I'm in the level 12 documentation for JSON resources and if I go to the pagination item here, this shape here, data, links, and meta is exactly what I want. So I will copy the pagination deep link here and I'm sure there's ways to generate this, but here I will turn to chat GPT to generate this for us.
and I'm sure there's ways to generate this, but here I will turn to chatGPT to generate this for us and I think it's gonna do a good job. So generate front end types for Laravel 12 pagination. I am building a React and Inertia app and want the type signature for paginated results to use in the front end. And I'll paste the link to the documentation and I think it's going to generate exactly what we want.
And I'll paste the link to the documentation and I think it's going to generate exactly what we want. Let's check it out. Alright, so it's identified what seems exactly correct and so it's proposing us pagination links, meta link. There we go. This is what I want a data which is an array of some sort of model, which here is going to be puppies and then pagination, meta and pagination links. All right, that sounds great. So I will copy this and in the types/index file
All right, that sounds great. So I will copy this and in the index file after our filters, I will paste this. So let's see if we can go and use this paginated response in our index page right here. So here where we receive the puppies, it's now going to be a paginated response that accepts the Puppy model inside of it. And I think that's going to be extremely useful.
Refactoring Components for Pagination3:59
that accepts the Puppy model inside of it. And I think that's going to be extremely useful to refactor our app. So let's remove the pret tag and bring back the actual components. And so what I think I will do here is instead of inertia puppies, I will call these now paginated puppies. So that's a little bit clearer. So of course we need to go and update our main components to accept paginated puppies.
So of course we need to go and update our main components to accept paginated Puppys. And those paginated Puppys are going to be once again a paginated response that accepts the Puppy model. Alright, great. And you can see copilot knows already that we want to replace our Inertia Puppys, but then reach for the data key because the Puppys are wrapped in data. So we're going to do exactly this.
because the puppies are wrapped in data. So we're going to do exactly this. I'm going to accept this change. So instead of passing all the puppies, we just passed the data key because there is also the meta and links properties within the paginated puppies. And so what we've done now is set the puppy states back to just the puppies data like it was before. Hopefully that makes sense. We are now receiving more information about the puppies,
Hopefully that makes sense. We are now receiving more information about the puppies, but we want the puppy states to still just be the puppies data. So we've done that change and let's keep going, making the changes for our app to work again. So I will scroll down. The search just has filters, no problems here. Next, our short list probably also just needs
The search just has filters, no problems here. Next, our short list probably also just needs the puppies data. So paginated puppies, dots. And you can see thanks to our paginated puppies type TypeScript telling us exactly what's available. This is why we've created the types. So we're going to pass the data to the short list and then co-pilot suggest the same change for both the puppies list and NewPuppiesForm.
and then co-pilot suggest the same change for both the puppies list any new puppies form, I will accept that. But actually the puppies list needs to have the entire paginated puppies, the data and the meta and links, which means the app is still broken because we are not going to be able to iterate over the puppies that way. The front end should still have the exact same error. And yes it does, but hopefully you follow along.
The front end should still have the exact same error. And yes it does, but hopefully you follow along. And now we can go in our puppies list and update this puppies expected type to be a paginated response of puppies. So once again where we receive the puppies, we need to tell the components that this is no longer true. We now have a paginated response of the Puppy model. And armed with that knowledge copilot, now understand that we need to reach for puppies that that's data.
And armed with that knowledge copilot, now understand that we need to reach for puppies that that's data before we can map over it. And at this point we should be back to the point where our app works exactly like it did before, but we now have more information about this paginated puppies in our application and in our PuppiesList components. So let's check that the PuppiesList is showing once again and yep, they are the puppies.
So let's check that the puppies list is showing once again and yep, they are the puppies. And if I refresh the console is now clear of errors, which is very good and I think the app should work just like before. So let's search for food. Are you telling me no dog likes food play? Alright and everything works, but obviously none of the hundred and nine dogs we have likes food, which is a bit concerning.
but obviously none of the hundred and nine dogs we have likes food, which is a bit concerning. Okay, you probably did not notice this because I haven't scrolled down, but something really interesting has happened already in our app. So let me empty the search filtering and show you what I mean. Right now we have 1, 2, 3, 4 results for play and if I empty the search
Right now we have 1, 2, 3, 4 results for play and if I empty the search or I can clear it, remember how we had a hundred puppies. Now lemme scroll and we have 3, 6, 9, 12, 15 puppies. That's it. Our filter search is empty, but we're still only loading 15 puppies on the page. That is pagination at play. 15 is the default number that will come in pagination, but if I was adding a number here like paginate(6), you can see the page has already updated.
but if I was adding a number here like paginate six, you can see the page has already updated and we only have six puppies. There's 109 puppies in the database, but we are only loading six. And however I can still search for love's and it'll find six result maximum that meets the search criteria. So this right there is super powerful. We only load six puppies, but
So this right there is super powerful. We only load six puppies, but whenever we perform a search we are going to search through the 109 puppies available in the database client side. This would not be possible. You would need to load all the puppies to be able to filter by your search term and make sure you find all the puppies in the database that match the results. So that is the massive, massive improvement.
Building Pagination UI8:24
in the database that match the results. So that is the massive, massive improvement and win of doing search and filtering on the server side. Obviously right now the UX is not great because we don't have any pagination information so it almost feels like there is only six puppies in the app, especially if the filter is empty like this. So let's go and implement actual pagination UI. Next I want something that looks nice but I don't wanna spend too much time doing custom UI work.
Next I want something that looks nice but I don't wanna spend too much time doing custom UI work. So we could use something from Tailwind UI or maybe because we're using the React start kit, use some Chakra UI components. So if I go to ui.chakra.com and search for pagination, you can see that there is a pagination component. Now this is kind of nice, but honestly it's probably a little bit too much for our use case.
Now this is kind of nice, but honestly it's probably a little bit too much for our use case. We don't really need all of these components and also there'll be a little bit of trickery to do to make it work with Inertia links properly. So what I think we'll do is create our own minimal pagination components. We will use ChatCN UI components like the buttons, but all we need is a previous button, a next button and maybe some information in the middle on how many pages.
but all we need is a previous button, a next button and maybe some information in the middle on how many pages of results are available. So let's build that together. I will go find the components directory in resources components. And so I'm going to create a new file called pagination.tsx, alright, we will export a function called Pagination and we'll start by rendering a pre or return pre
function called pagination and we'll start by rendering a pre-tax or return pre and we'll json, that string dify, uh, it'll be called the pation. Now two lowercase P and we will receive here this pagination, which can be a paginated response but from the puppy type. So we need to import the puppy and the paginated response. Alright, so I've just created a component that expects a pagination prop,
Alright, so I've just created a component that expects a pagination prop, which is the paginated response and then stratifies the JSON output for it. So let's go in our puppies list and I will add a fragment so I can have multiple top level elements and after the ul, I want my pagination component. And yeah, why not For now we will pass the whole puppies. This is too much information TMI
And yeah, why not For now we will pass the whole puppies. This is too much information TMI because we don't really want the data, we just want the meta and links. But let's start with that. So if I refresh the page after the six puppies, we should have a string file of the puppies data, which we don't want, and then the links and meta, which is exactly what we want to start figuring out how we want to build this pagination ui.
to start figuring out how we want to build this pagination ui. Okay, let's fine tune the ergonomics of this pagination component. So let's go meta and this is going to be puppies.meta and then links. And this is going to be puppies.links. And so let's go update our pagination components. Maybe I'll have a dedicated type signature at the top.
And so let's go update our pagination components. Maybe I'll have a dedicated type signature at the top. PaginationProps. So it's going to have meta and this is going to be, I believe that we do have a PaginationMeta interface. Yep. And also links. So we can use these two. So meta is going to be PaginationMeta, and then links is going to be PaginationLinks like so.
and then links is going to be pagination links like so. And I also want to be able to add some classes to the pagination component when we use it just to add things like margin top or whatever. So let's also pass a className prop that we can customize. So className, this is optional and it's a string. Alright, so now down here because I have a dedicated type signature, I can replace this with PaginationProps like so.
because I have a dedicated type signature, I can replace this with pagination props like so. And let's try to wrap our pret tag in a div. And here we will add a className attribute. So let's go flex items center and justify between. But we want to also merge these classNames passed from the components directly. So here I'm going to use the cn function from shadcn so that I can have default classes but also pass the pagination.
so that I can have default classes but also pass the pagination. That class name and the CN function will make sure that there is no conflict and these styles are merged together. Okay, our pagination component is a little bit more elaborate and so let's go check how it looks like and if we can customize it. Whoops, we got a problem. Now of course it's not receiving pagination anymore.
Whoops, we got a problem. Now of course it's not receiving pagination anymore because the type is not that, but just pagination props. And so it's receiving separately meta, links, and class name attributes like so, which means we also need to update our components to just get the class name here. And instead of stratifying the pagination, we string five meta and links. This is now correct apologies for this oversight. So let's try again.
This is now correct apologies for this oversight. So let's try again and see how a pagination component behaves. All right, this time it's working properly. So I will scroll down and this time we don't have the data anymore, we just have the meta and then the links information. And let's go try to see if we can pass some custom styles from the component.
And let's go try to see if we can pass some custom styles from the component. So in the IES list, I will try here to go class name equals bg-red-500. This is going to look terrible but it's going to show us that it works. Alright, sorry for fiddling around like this, but we are now ready to implement the pagination UI. I'll remove the bg-red-500, but instead I will go empty-dash-8.
I'll remove the bg-red-500s, but instead I will go empty-8 to create some spacing from the puppies list. All right, that's a bit better. And I don't really care right now about the meta, but I'm interested in the links data specifically the next and previous links. This is really all I care, mostly to have a pagination that gives a previous and next buttons to navigate between the results.
that gives a previous and next buttons to navigate between the results. So let's use that information and create a couple of buttons that try to navigate to the prev link or next link. All right, so in our pagination component, I'll get rid of the string defined data and TypeScript should make it easy for us to know what's happening. So we already have a flex container,
to know what's happening. So we already have a flex container, which means I can have a div on the left and a div on the right. And the justify-between is going to push both on each sides. So we've seen that the links can be null if there aren't any. Like here on the page one, we don't have a previous link, so it's null. And I imagine when we are on the last page,
we don't have a previous link, so it's null. And I imagine when we are on the last page, next would be null too. So let's do that. If we have links, do prev. In other words, if it's not null, let's render a ChatCN button component that goes to links.prev. Now I don't want this to be a button and I also don't want this to be an anchor tag. I want this to be an Inertia link.
and I also don't want this to be an anchor tag. I want this to be an InertiaLink. Luckily the Button component from Chakra UI lets us pass an child prop, which is going to offset the rendering of the tag to the child. So we can go button's child and inside of the have an InertiaLink component. So let's add this as child, which means that now I can have an InertiaLink component, which is going to take an href of links.
that now I can have an Inertia link component, which is going to take an H rev of links that prev like so. And I need to put the links that prev in the middle, except we don't want the text to be links that prev, that's the URL, we just wanted to say previews. Hopefully that makes sense. If there is a links that prev, which means we are not on the first page, we render a button that offsets the rendering to the link tag from Inertia
which means we are not on the first page, we render a button that offsets the rendering to the link tag from Inertia navigates to the links that prevUrl that comes from that links data and says previous. And we can do exactly the same for the next logic. So in the second div, if we have link that next, we are going to display a link that goes to the next page that I believe should suffice to have a minimal pagination UI. Let's go check it out.
pagination ui. Let's go check it out. All right, let's scroll down and we have a next button. And if I click on next, whoops, we've scrolled to the top of the page, but you know why, and we should be on the second page here, which means we have now next and previous and the URL has a page=2 parameter, which is great. Let's go next one more time. We should be on page three and yes we are.
Let's go next one more time. We should be on page three and yes we are. You can see the puppy is changing and I can go previous back to page two and back to page one, which means previous is going to disappear because we are on page one. Okay, this is already very functional. I wanna make a few couple of changes. I want the buttons to be less dominant, so maybe we'll use a variance like ghost.
I want the buttons to be less dominant, so maybe we'll use a variance like ghost. And then I also want to display in the middle between the two buttons, the information like saying page one of six to just give information to users, how many roughly results there are available. Okay, quickly, both buttons here. I will add the variant off and you can see TypeScript at work.
I will add the variant off and you can see TypeScript at work giving us all the options. And yeah, let's go ghost here, which is sort of less in your face. Uh, and let's remove this closing tag. So the buttons are not going to feel like text link, but when you hover over it, there is a nice subtle background. Let's also fix the scrollToTop.
there is a nice subtle background. Let's also fix the scroll to top. And here I don't even have to tell you how to fix this because we fixed that exact problem already twice at least. So for both our link tags, we are going to preserve scroll to preserve the scroll position. That in itself is going to make our navigation feel a lot more functional. Next, next, previous, previous, previous. How cool. That works really well. Alright,
Next, next, previous, previous, previous. How cool. That works really well. Alright, and so now in between the previous link and the next link in the middle here, thanks to justify-between, I'm going to have a <p> tag that is going to say page two of 12, but of course we're going to make it dynamic. I think this comes from the meta and like I said, TypeScript is going to document for us what is available.
and like I said, TypeScript is going to document for us what is available. So exactly that. I think we want the current page. You are currently on the current page of meta, that last page. So it's going to say page one of six, page two of six, page three of six. And maybe let's give some classes to the paragraph text small and maybe medium. That should look nice and let's go check it out.
to the paragraph text small and maybe font medium. That should look nice and let's go check it out. And at this point we should have a pretty cool pagination page one of 20, page two of 20, page three of 20. And if I was to change the pagination increments to let's say 9 per page, we are now showing 9 per piece per page, which means there is less pages, there are 14 pages total and everything works really nicely. Awesome. Let's add a cherry on top by adding some icons.
and everything works really nicely. Awesome. Let's add a cherry on top by adding some icons to the previous and next buttons. So the previous is going to have a caret pointing to the left and then the next is going to have a caret pointing to the right. And so let's start with previous, I'm going to wrap the previous word in a span tag so that it's treated like a node. And before that I will have a chevron-left
that it's treated like a node. And before that I will have a Chevron left icon from Lucid React. And yeah, let's give it a class of size four. And I believe the button component from Chatson UI is factoring in SVG icons. So we don't have to have a flex container with a gap. It should nicely lay them out and vertically send to them. Yeah, very nice look at this. And let's do the same for the next button.
Yeah, very nice look at this. And let's do the same for the next button. So we'll scroll down, wrap next in a span tag. This time we'll add the ChevronRight icon after the next text. Very nice. And I think I just wanna reduce the marginTop a little bit so it's tiny bit closer to the actual PuppiesList component. So that happens in the PuppiesList component where we pass empty 8,
So that happens in the PuppiesList component where we pass emptyEight, I think emptySix will do just fine here. And with that my friends, we have a functional and gorgeous pagination navigation. It works really well and the very best part is it's going to adapt to the result from the search. So if I narrow it down to loves, we might still have a couple of pages,
Preserving Query Parameters20:31
So if I narrow it down to loves, we might still have a couple of pages, we actually have three pages and the pagination aha. And actually you can see that there is a problem. We have lost our search. Let's try that one more time. Loves the filtering is done. I scroll down, there is three pages. I will click on the next result and boom, we've jumped to 14 and we have lost the search filtering query parameter. And if we look at the URL, we only have the page.
and we have lost the search filtering query parameter. And if we look at the URL, we only have the page. It has wiped out the search equals loves. That should provide. Now that I have both parameters, the search filtering and the page two. So this is the last frontier that is going to make pagination super solid. We need it to respect and honor the existing query parameters passed to the URL.
We need it to respect and honor the existing query parameters passed to the URL. So when it's app.pending page equals two, it shouldn't wipe the other parameters like the search one. And while Laravel being Laravel, this could not be easier if I go to the PuppyController index here after paginates the result, all I have to do is say give them to me with queryString. And basically this is going to do exactly what we want. It's going to make sure
And basically this is going to do exactly what we want. It's going to make sure that it preserves the query string when it appends the pagination page. So just like this, let me get rid of any query parameters and I'm going to search for love. I will scroll down to the pagination and I will click on next. And as you can see, we've preserved the search parameter as well as the page parameter.
And as you can see, we've preserved the search parameter as well as the page parameter. So now our pagination works really well and we will only ever display nine results maximum and paginate the rest of the results whether there is a search query or not, which I think is extremely powerful. Alright, I told you that you might enjoy this lesson and I really hope you did. I personally really find pagination super empowering.
that you might enjoy this lesson and I really hope you did. I personally really find pagination super empowering. Coming from a non Laravel world discovering this and how easy it is to work with Inertia was pretty mind blowing to me. So I hope you enjoyed it and I will see you in the next lesson.
