
Join the Conversation!
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
By now you should have a good understanding of how to use Prisma to interact with your database. You've dealt with a variety of queries. Creating, reading, updating, and deleting data. You've also learned how to filter data using .
In this lesson, we're going to learn how to paginate data. Pagination is a common technique used to break up large datasets into smaller, more manageable chunks.
We'll be using this pattern in our application to display a list of items in the search page.
Take a look at the page .
You'll notice we have filters to the left- to sort our products by:
And to the right, we have some placeholder product display components.
The application is already setup to push whatever we select in the filters to our URL, so our main goal will be integrating this logic with our backend and database.
Let's begin by simply fetching multiple products from our database.
products.ts;
export async function getProducts() {
try {
const allProducts = await prisma.product.findMany({
include: {
images: true,
reviews: true,
},
});
return allProducts;
} catch (error) {
throw new Error(error);
}
}
Here we're using the method to fetch all products from our database. We're also using the option to fetch the images and reviews associated with each product.
Remember, the option is used to fetch related records from other tables. This is a powerful feature of Prisma that allows us to fetch related data in a single query.
We need this data to display the product images and ratings on our search page.
Let's begin by cleaning up the data so that we can display what we need to in our search page.
products.ts;
export async function getProducts() {
try {
const allProducts = await prisma.product.findMany({
include: {
images: true,
reviews: true,
},
});
const products = allProducts.map((product) => ({
...product,
rating:
Math.floor(
product.reviews.reduce((acc, review) => acc + review.rating, 0) /
product.reviews.length,
) || 0,
image: product.images[0]?.url,
}));
return products;
} catch (error) {
return [];
}
}
Here we're mapping over the products array and calculating the average rating for each product. We're also setting the property to the URL of the first image in the images array.
This way, the data we return from the function will be clean and ready to be displayed on our search page.
Now that we have our data ready, let's integrate it with our search page.
Let's open up .
page.tsx
import ProductResult from "@/components/search/ProductResult";
import SearchFilters from "@/components/search/SearchFilters";
// import our getProducts function
import { getProducts } from "@/lib/actions/products";
export default async function Component() {
// fetch products
const products = await getProducts();
return (
<div className="grid md:grid-cols-[300px_1fr] gap-8 px-4 md:px-8 py-20">
<div className="bg-white rounded-lg shadow-sm dark:bg-gray-950 p-6 space-y-6">
<SearchFilters />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
// map over products and render ProductResult component
{products.map((product) => (
<ProductResult key={product.id} product={product} />
))}
</div>
</div>
);
}
Let's now open our component:
ProductResult.tsx
import Link from "next/link";
import Stars from "@/components/product/Stars";
export default function ProductResult({ product }: { product: any }) {
return (
<div className="bg-white rounded-lg shadow-sm dark:bg-gray-950 overflow-hidden">
<Link className="block" href={`/product/view/${product.id}`}>
<img
// display product image
src={product.image}
alt="product"
className="w-full h-full object-cover"
/>
<div className="p-4 space-y-2">
// display product name and rating
<h3 className="font-semibold text-lg">{product.name}</h3>
<div className="flex items-center gap-1">
<Stars rating={product.rating} />
<span className="text-sm text-gray-500 dark:text-gray-400 ml-2">
{product.rating}
</span>
</div>
</div>
</Link>
</div>
);
}
Now if you navigate to the search page, you should see a list of products displayed with their names and ratings.
The first step to pagination is to understand and .
is used to skip a certain number of records in the database. is used to limit the number of records returned.
For example, if we want to fetch the first 10 products, we can use the following query:
//
const products = await prisma.product.findMany({
include: {
images: true,
reviews: true,
},
skip: 0,
take: 10,
});
If we want to fetch the next 10 products, we can use the following query:
//
const products = await prisma.product.findMany({
include: {
images: true,
reviews: true,
},
skip: 10,
take: 10,
});
We can use these two options to implement pagination in our application.
Let's start by deciding how many products we want to display per page.
products.ts;
export async function getProducts() {
const resultsPerPage = 5;
const skip = 0;
try {
const allProducts = await prisma.product.findMany({
include: {
images: true,
reviews: true,
},
skip,
take: resultsPerPage,
});
const products = allProducts.map((product) => ({
...product,
rating:
Math.floor(
product.reviews.reduce((acc, review) => acc + review.rating, 0) /
product.reviews.length,
) || 0,
image: product.images[0]?.url,
}));
return products;
} catch (error) {
return [];
}
}
Here we've added a variable to determine how many products we want to display per page.
We've also added a variable to determine how many products we want to skip.
Next, let's import a argument into our function to determine which page we want to display.
products.ts;
export async function getProducts({ page = 1 }) {
const resultsPerPage = 5;
const skip = (page - 1) * resultsPerPage;
try {
const allProducts = await prisma.product.findMany({
include: {
images: true,
reviews: true,
},
skip,
take: resultsPerPage,
});
const products = allProducts.map((product) => ({
...product,
rating:
Math.floor(
product.reviews.reduce((acc, review) => acc + review.rating, 0) /
product.reviews.length,
) || 0,
image: product.images[0]?.url,
}));
return products;
} catch (error) {
return [];
}
}
This is the core of our pagination logic:
//
export async function getProducts({page=1}) {
const resultsPerPage = 5;
const skip = (page - 1) * resultsPerPage;
...
}
Here we're calculating the value based on the argument.
For example, if we want to display the first page, we'll set the argument to 1, and the value will be 0.
If we want to display the second page, we'll set the argument to 2, and the value will be 5.
Now let's integrate this with our search page by grabbing the query parameter from the URL.
page.tsx
export default async function Component({
searchParams,
}: {
searchParams: {
name: string;
category: string;
minRating: string;
page: string;
};
}) {
// parse page query parameter
const page = parseInt(searchParams.page) || 1;
const products = await getProducts({ page });
...
}
Here we're parsing the query parameter from the URL and passing it to the function.
Now if you navigate to the search page and add the query parameter to the URL, you should see the products change based on the page number.
You can manually change the query parameter in the URL to see the products change.
In the next lesson, we'll learn how to add pagination controls to our search page to make it easier for users to navigate between pages.
"Please login to view comments"
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.