My goal is straightforward: I want to be able to filter my Prisma database based on parameters passed in the URL when I click on a CategoryBox
.
After clicking on a CategoryBox
, my URL does change to something like
http://localhost:3000/?category=XYZ
, but my list does not update to show objects in the category of XYZ
.
The problem seems to lie with SearchParams
being undefined in page.tsx
.
File src/app/page.tsx
// Main home page (index)
import ClientOnly from "@/app/components/ClientOnly";
import Container from "@/app/components/Container";
import EmptyState from "@/app/components/EmptyState";
import getListings, { IListingsParams } from "@/app/actions/getListings";
import ListingCard from "@/app/components/listings/ListingCard";
import getCurrentUser from "@/app/actions/getCurrentUser";
interface HomeProps {
SearchParams: IListingsParams;
}
const Home = async ({ SearchParams }: HomeProps) => {
const listings = await getListings(SearchParams);
const currentUser = await getCurrentUser();
if (listings.length === 0) {
return (
<ClientOnly>
<EmptyState showReset />
</ClientOnly>
);
}
return (
<ClientOnly>
<Container>
<>
<div
className="grid">
{listings.map((listing: any) => {
return (
<ListingCard
currentUser={currentUser}
key={listing.id}
data={listing}
imageSrcList={listing.imageSrc}
/>
);
})}
</div>
</>
</Container>
</ClientOnly>
);
};
export default Home;
File src/app/components/navbar/Categories.tsx
"use client";
import CategoryBox from "../CategoryBox";
import Container from "../Container";
import { usePathname, useSearchParams } from "next/navigation";
import { GiFactory } from "react-icons/gi";
type CategoryType = "A" | "B";
interface CategoriesProps {
selectedOption: CategoryType;
visible: boolean;
}
export const categories = {
A: [
{
label: "XYZ",
icon: GiFactory,
description: "...",
},
],
B: [
{
label: "ABC",
icon: GiFactory,
description: "...",
},
],
};
const Categories: React.FC<CategoriesProps> = ({ selectedOption, visible }) => {
const params = useSearchParams();
const category = params?.get("category");
const pathname = usePathname();
const isMainPage = pathname === "/";
if (!isMainPage || !visible) {
return null;
}
return (
<Container>
<div className="pt-4 flex flex-row items-center justify-between overflow-x-auto">
{categories[selectedOption].map((item) => (
<CategoryBox
key={item.label}
label={item.label}
icon={item.icon}
selected={category === item.label}
/>
))}
</div>
</Container>
);
};
export default Categories;
File src/app/components/CategoryBox.tsx
"use client";
import qs from "query-string";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback } from "react";
import { IconType } from "react-icons";
interface CategoryBoxProps {
icon: IconType;
label: string;
selected?: boolean;
}
const CategoryBox: React.FC<CategoryBoxProps> = ({
icon: Icon,
label,
selected,
}) => {
const router = useRouter();
const params = useSearchParams();
const handleClick = useCallback(() => {
let currentQuery = {};
console.log("params", params);
if (params) {
currentQuery = qs.parse(params.toString());
}
const updatedQuery: any = {
...currentQuery,
category: label,
};
if (params?.get("category") === label) {
delete updatedQuery.category;
}
const url = qs.stringifyUrl(
{
url: "/",
query: updatedQuery,
},
{ skipNull: true }
);
router.push(url);
}, [label, router, params]);
return (
<div
onClick={handleClick}
className={`
flex
flex-col
items-center
justify-center
gap-2
p-3
border-b-2
hover:text-neutral-800
transition
cursor-pointer
${selected ? "border-b-neutral-800" : "border-transparent"}
${selected ? "text-neutral-800" : "text-neutral-500"}
`}>
<Icon size={26} />
<div className="font-medium text-sm">{label}</div>
</div>
);
};
export default CategoryBox;
File src/app/actions/getListings.ts
import prisma from "@/app/libs/prismadb";
export interface IListingsParams {
userId?: string | undefined | null;
category?: string;
}
export default async function getListings(params: IListingsParams) {
try {
const {
userId,
category,
} = params || {};
let query: any = {};
if (userId) {
query.userId = userId;
}
if (category) {
query.category = category;
}
const listings = await prisma.listing.findMany({
where: query,
orderBy: {
createdAt: "desc",
},
});
const safeListings = listings.map((listing) => ({
...listing,
createdAt: listing.createdAt.toISOString(),
}));
return safeListings;
} catch (error: any) {
throw new Error(error);
}
}
I am currently following a video tutorial to learn more about this, although I believe I may have missed a crucial step or misunderstood some part.
To further clarify, the GitHub repository for the tutorial I am following can be found here: https://github.com/AntonioErdeljac/next13-airbnb-clone/tree/master
This project marks my initial attempt at a CRUD application using next.js and it has presented some challenges that I am working through.