Dynamic Filtering items with prisma and Nextjs?
Oi everyone! I use prisma in combination with Nextjs and mongodb on a project that deals with animal adoption. Namely, now I would like to do dynamic filtering by certain categories and enable it to interact with the user on the client side. Well, I'm interested in whether it's feasible to do it using prism, since I found out through the documentation that I can filter out certain things via the
where: { }
query.
Any help and suggestions on how to do this are welcome. Thank you all!50 Replies
yeah, you can @tom_bombadil
I can recommend best libraries for your case
https://nuqs.47ng.com/
this library will be very helpful for you to store values for filtering in the url
nuqs | Type-safe search params state management for Next.js
Type-safe search params state management for Next.js. Like React.useState, but stored in the URL query string.
and then in your server component you can use prisma to fetch your data while filtering out by query params in the url.
let me know if you have any questions
@James4u thank you for answer! Allow me to explain to you what the current situation is on my project, so you can perhaps explain to me 'closer' in which direction I should go.
Actually, I have specifically 4 filters, i.e. 4 categories by which I want to filter the items from the array that I display, which are (category: Dog, Cat, Others; location: some cities; ages: young, adult; gender: female and male) . What I managed to do with prisma is that when I set it up in the function on the server side like this:
export async function getAdoptPost(
context: any,
location?: string,
) {
const page = parseInt((context.query.page as string) || "1", 12);
const pageSize = 12;
const total = await db.adoptAnimal.count({});
const post = await db.adoptAnimal.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
where: {
location: location,
},
orderBy: {
createdAt: "desc",
},
});
return {
post,
page,
pageSize,
total,
};
}
this is an example where I only put 'location' as one of the possible options in the filter and now on the client side if I call this function 'getAdoptPost' and assign it 'location' something like this:
const [location, setLocation] = useState<string>("Boston");
const fetchData = (page: number) => {
startTransition(async () => {
const result = await getAdoptPost({
query: { page },
location,
});
setPosts(result.post);
setPage(result.page);
setPageSize(result.pageSize);
setLocation(location);
setTotal(result.total);
setIsLoading(false); // Set loading to false after data is fetched
console.log(result);
console.log(location);
});
};
because of this state, in which I hard-coded the location 'Boston', it will filter out all the animals that have that location on them, but I would like to make it so that the user selects that location via the filter below, and I get the results.are you doing client side data-fetching?
that I also have a FIlterMenu component on the client side where there are fields with filters and through these fields I collect the data that the user picks up from the filter. And this is a function that is called on the submit FIlterMenu and I put all those filters that the user selects in the 'allFilters' array
export const updateFilters = async (formData: FormData) => {
const categoryFilters = formData.getAll("category");
const locationFilters = formData.getAll("location");
const spolFilters = formData.getAll("spol");
const starostFilters = formData.getAll("starost");
const allFilters = [
...categoryFilters,
...locationFilters,
...spolFilters,
...starostFilters,
];
console.log("all filters", allFilters);
if (allFilters.length > 0) {
const params = new URLSearchParams([
["category", categoryFilters.join(",")],
["location", locationFilters.join(",")],
["spol", spolFilters.join(",")],
["starost", starostFilters.join(",")],
]);
console.log("params", params);
redirect(
/adoptPet?${params.toString()});
} else {
redirect("/adoptPet");
}
};
Is this looks well?sorry @tom_bombadil can you format your code?
yes I do
there is also some extra code related to
onSubmit
in the FilterMenu, ignore it for now because I'm calling the 'actions' function 'updateFilters' on onSubmit
right nowyou mean, you use server actions?
yes, that 'updateFilters' function is on the server side
should I use classic handleSubmit in the Filter Menu?
or this is fine?
server actions are for data mutation indeed - you are just redirecting right? you can do that in the client side
this is the log of that updateFilters function:
all filters [ 'Macka', 'Tuzla', 'Zensko', 'Odraslo' ]
params URLSearchParams {
'category' => 'Macka',
'location' => 'Tuzla',
'spol' => 'Zensko',
'starost' => 'Odraslo' }
middleware is running on route: /adoptPet
GET /adoptPet?category=Macka&location=Tuzla&spol=Zensko&starost=Odraslo 200 in 29ms
in 'allFilters' I place what the user has chosen as a filter and then fill in the pathname as you can see below@tom_bombadil sorry but you are not answering my questions - difficult to keep conversation in efficient way
what questions?
checked this?
let's go step by step
okay, understand, you suggest to do this collecting data from the user directly on the FilterMenu component?
no need for server actions?
follow these @tom_bombadil
- use https://nuqs.47ng.com/ this library and replce all of your states for filtering
- when they click on button (or form submit), just
router.refresh()
- use those query params to filter out your data with prismanuqs | Type-safe search params state management for Next.js
Type-safe search params state management for Next.js. Like React.useState, but stored in the URL query string.
yeah, server actions are for data mutation - you are only doing redirection in your current codebase which can be also done in the client component
follow above steps - step by step and let me know if you have any trouble
okay I will go with this and let you know if I get stuck
@James4u I replaced all states in the FilterMenu component with
useQueryState
:
const [category, setCategory] = useQueryState("category");
const [location, setLocation] = useQueryState("location");
const [gender, setGender] = useQueryState("gender");
const [age, setAge] = useQueryState("age");
what did you mean by 'submit' to do just router.refresh()
? you mean here to call router.refresh() :
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setCategory(category);
setLocation(location);
setGender(gender);
setAge(age);
console.log(category, location, spol, starost);
haha @tom_bombadil please format your code when you post here from next time
and hmm why do you set states in handleSubmit?
those should be used in inputs
just to see if i am getting value from jsx
<div className="flex flex-col flex-1">
<label htmlFor="category" className="mb-1 text-black text-[12px]">
Kategorija
</label>
<select
id="category"
name="category"
value={category || ""}
onChange={(e) => setCategory(e.target.value)}
className="p-2 border rounded-[24px] bg-white text-[#A4A4A4] text-[12px]"
>
<option value="Pas">Pas</option>
<option value="Macka">Mačka</option>
<option value="Ostalo">Ostale životinje</option>
</select>
</div>
oh you are doing that! if you see the values (what you type) in the input, it's working correct
yes
but what with router.refresh()
don't need to set states in the submit handler
I will remove it
what is next step bro 😄
and where do you fetch data?
using prisma
on the parent component
show me the code
sure
please format your code 🙏
i thought i always format by tagging everything and taking the '<>' sign, is that ok?
nonpe
with single ``
3 `
and file extension
oh
didn't know that
sry mate
will do now
this is the page where I render all animals and where want to filter that 'posts' array
and this is server function which using prisma
@James4u should provide you some more code?
oh sorry couldn't see the noti - give me some time
sure
tnx mate
hmm you are doing client side data fetching
not a deal breaker but you can use server side data fetching if you are using app router
if we can leave it on the client side, then let it stay
if it will not create a problem related to filtering
and also
you have isPending - why you need another state for the loading state?
setIsLoading(false); // Set loading to false after data is fetched
btw why don't you pass filter params to the api so that you can use them when you get data from prisma?
or was it your original question?
yes, that's actually my question, how will I now apply my filters that I picked up in the FilterMenu, on this AdoptPet page where all the animals are rendered. It is not the clearest to me with the prisma of how to bind, if you can help me with that?
so before you hit the api, here
you can get query params from the url, right?
Functions: useSearchParams | Next.js
API Reference for the useSearchParams hook.
sorry, what do you want to say?
http://localhost:3000/adoptPet?category=Dog&location=Sarajevo&spol=Man&starost=Adult
if you ask for this, I'm getting params in the pathame while picking up filter values from FilterMenu
there is also pagination, which is done in the same function on prisma, so don't get confused 🙂
and can you give a little more explanation of what you mean by 'hit the api'?
@James4u lemme know when you're available to continue discuss about this?
you need to pass more fitler values when you hit this endpoint, right?
it is clear to me that I have to send the filtered values, how would the function in prisma look then, something like this?
and then on the client side:
@James4u
Yeah, they seems to be correct
but again, you may not need
isLoading
stateI will remove it, but that doesn't affect the filtering problem