Fetching Contentful data with App directory
Unanswered
Hygen Hound posted this in #help-forum
Hygen HoundOP
Hey everyone, I'm relatively new to Next.js and currently learning my ways around the app directory and the features.
Right now I am not sure how to implement contentful and map the items. Here's how I am doing it right now and I realized that getStaticProps is not available in the app router. Any help would be very much appreciated.
Here is the code that I have:
Right now I am not sure how to implement contentful and map the items. Here's how I am doing it right now and I realized that getStaticProps is not available in the app router. Any help would be very much appreciated.
Here is the code that I have:
import { createClient } from "contentful";
export async function getStaticProps() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_KEY,
})
const res = await client.getEntries({
content_type: 'resourcesPage'
})
return {
props: {
resources: res.items
}
}
}140 Replies
@Hygen Hound Hey everyone, I'm relatively new to Next.js and currently learning my ways around the app directory and the features.
Right now I am not sure how to implement contentful and map the items. Here's how I am doing it right now and I realized that getStaticProps is not available in the app router. Any help would be very much appreciated.
Here is the code that I have:
import { createClient } from "contentful";
export async function getStaticProps() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_KEY,
})
const res = await client.getEntries({
content_type: 'resourcesPage'
})
return {
props: {
resources: res.items
}
}
}
fetch your contentful data directly from the page.tsx server component if you're using the app directory, make sure to mark the page component as async and just await the data in there. Then you can map over it and display whatever you need to
or if only a certain component needs the contentful data, fetch it directly within that component instead of at the page level
With the app directory you can really make sure that only the components that need the data fetch/access it.
Hygen HoundOP
@Plague Awesome this is really helpful, thanks so much! I'll give it a try 

Hygen HoundOP
Thanks so much man here's the solution that made it work for anyone who's also may have the same problem as me:
in page.jsx/tsx
in page.jsx/tsx
import { createClient } from "contentful";
async function fetchContentful() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_KEY,
})
const res = await client.getEntries({
content_type: 'resourcesPage'
})
return res.items
}
export default async function Home() {
const resources = await fetchContentful()
console.log(resources);
return (
<main>{...}</main>
)
};@Hygen Hound Thanks so much man here's the solution that made it work for anyone who's also may have the same problem as me:
in page.jsx/tsx
import { createClient } from "contentful";
async function fetchContentful() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_KEY,
})
const res = await client.getEntries({
content_type: 'resourcesPage'
})
return res.items
}
export default async function Home() {
const resources = await fetchContentful()
console.log(resources);
return (
<main>{...}</main>
)
};
Yeah I also use contentful as my CMS, but, this problem would of happened to anyone with any data fetch
Seen this asked a lot so no worries 

Hygen HoundOP
@Plague Hey man im back with another question hahaha
how would you approach adding category filtering in your application? For instance I have a list of cards that are dynamically mapped from contentful and I would like to filter them by category using the useSearchParams to store the query in the url
e.g https://website.com/category?=tools
e.g https://website.com/category?=tools
I'm still quite confused on whether if I should fetch the data in the Card component or the page.jsx
Here's my code so far:
My page.jsx
My card component (where i just take in the props passed from the page.jsx):
My page.jsx
async function fetchContentful() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_KEY,
});
const res = await client.getEntries({
content_type: "resourcesPage",
});
return res.items;
}
export default async function Home() {
const resources = await fetchContentful();
return (
<main>
<div className="resource-card">
{resources.map((resource) => {
return <ResourceCard key= {resource.sys.id} resource={resource} />;
})}
</div>
</main>My card component (where i just take in the props passed from the page.jsx):
export default function ResourceCard({ resource }) {
const { title, category, thumbnail, tags } = resource.fields;
// Extract tag names from the tags reference field
const tagNames = tags.map((tag) => tag.fields.tag);
tagNames.sort()
return (
<Link href="/">{...}</Link>@Hygen Hound I'm still quite confused on whether if I should fetch the data in the Card component or the page.jsx
Unless your page.jsx needs that data as well move all the data fetching to your card component (assuming it’s a Server Component) and wrap it in a Suspense boundary that way the page won’t rely on that data and will load very quickly and you’ll just be waiting on the cards to show
With the app directory the mental model for data fetching has changed since you have such granular control over it, in the pages directory you had to do it at the page level if you wanted to fetch on the server, with the app directory you can do it at the component level meaning you should move data fetching to the Server Components that directly consume that data.
For your example what I would do is extract that Div that wraps the map and move all that logic into a ResourceContainer or ResourceGrid (depending on how you render the cards) component fetch the data in there map the items in there and then wrap the entire container in a suspense boundary in your page
Hygen HoundOP
Thanks so much man, this is really helpful. Now i understand a bit more clear.
Hygen HoundOP
Right now im trying to implement a filtering functionality that query the state of the category chosen by the users up to the Url using useSearchParams. From what I researched, I need to use the "use client" directive to use useSearchParams. So following your suggestion, would it be appropriate if I map the data in the ResourceCard component itself as a server component and then implement the filtering functionality in the ResourceContainer which would be a client component?
Once again thanks so much, im learning quite a bit in terms of nextjs client and server components haha
@Hygen Hound Right now im trying to implement a filtering functionality that query the state of the category chosen by the users up to the Url using useSearchParams. From what I researched, I need to use the "use client" directive to use useSearchParams. So following your suggestion, would it be appropriate if I map the data in the ResourceCard component itself as a server component and then implement the filtering functionality in the ResourceContainer which would be a client component?
Yeah this is an option, it's actually what I did orginially when I was using useSearchParams, I will say for my use case I decided to use Dynamic Routes instead of searchParams for the purpose of pre-rendering the first page of each category using generateStaticParams, then anything after that pre-render will be dynamically rendered. The problem I had personally with searchParams are it forces your entire route to be dynamically rendered which I did not want for the sake of querying the API on every request, especially since the contentful client doesn't use the fetch API so there is no request memoization, once
unstable_cache becomes stable this won't be that big of a problem since I can cache the results and revalidate the data with a tag, but, until then this is what I'm doing and honestly I find it much cleaner than searchParams and I gain all the beneifts of putting state in the URL just as route params instead of searchParams.That being said, you can wrap the ResourceContainer in a Suspense boundary so that the
useSearchParams hook doesn't make the entire route client-side rendered, but, you're going to have to read the searchParams from the page regardless so that'll make the route dynamic, but, it'll be Server-Side rendered.@Hygen Hound Once again thanks so much, im learning quite a bit in terms of nextjs client and server components haha
No problem man, glad you're learning.
Hygen HoundOP
@Plague thanks man, ill try to abstract out my code in a different file to play around with this. Will keep you in the loop if you don't mind haha
Hygen HoundOP
@Plague hahah awesome
im back with an update
I think I'm getting a bit closer in terms of implementing the filtering stuff
async function fetchContentful() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_KEY,
});
const res = await client.getEntries({
content_type: "resourcesPage",
include: 2,
});
return res.items;
}
export default async function Home() {
const resources = await fetchContentful();
const categoryCount = {};
// Iterate over resources and update categoryCount
resources.forEach((resource) => {
const category = resource.fields.category.fields.category;
categoryCount[category] = (categoryCount[category] || 0) + 1;
});
const router = useRouter();
const selectedCategory = router.query.category || "";
// Filter resources based on the selected category
const filteredResources = selectedCategory
? resources.filter((resource) => resource.fields.category.fields.category === selectedCategory)
: resources;
return (
<main className="my-20">
<section>
<div className="flex mb-8 justify-center">
{/* Filtering button, mapping the categories via contentful */}
{Object.entries(categoryCount).map(([category, count]) => (
<Link
key={category}
href={`/?category=${encodeURIComponent(category)}`}
className={`${
selectedCategory === category
? "border-text"
: "border-dim-gray"
}`}
>
<span className="text-sm">{category}</span>
<span className="text-xxs">{count}</span>
</Link>
))}
</div>
<div className="">
{filteredResources.map((resource) => (
<ResourceCard key={resource.sys.id} resource={resource} />
))}
</div>
</section>
</main>
);
}Right now i've abstracted my code so I can just fully focus down on the functionality part
so the home page here would technically be the ResourceContainer component
right now I am trying to filter the categories by using the useRouter() hook and push up the query onto the url depending on which category is selected
but the problem is that useRouter is only available in client component meanwhile the curernt component is a server component
in this case how do I make sure that I can use useRouter without clashing with the server components stuff?
would love your guidance here. Once again, thanks so much for helping me out haha
@Hygen Hound in this case how do I make sure that I can use useRouter without clashing with the server components stuff?
You're only using useRouter for reading the searchParams here it looks like:
1. You can get the searchParams in a page server component from props
2. If this was a client component you shouldn't use useRouter to read query params, instead you should use
If the Home component in this case would become the ResourceContainer like you're saying then I would suggest grabbing the searchParams from the parent page and then passing the searchParams down to the ResourceContainer.
1. You can get the searchParams in a page server component from props
2. If this was a client component you shouldn't use useRouter to read query params, instead you should use
useSearchParams. Your way wouldn't work even if you imported from next/router (the pages router version of the useRouter hook) since it's not compatiable with the app router model.If the Home component in this case would become the ResourceContainer like you're saying then I would suggest grabbing the searchParams from the parent page and then passing the searchParams down to the ResourceContainer.
Hygen HoundOP
ohhh okay that definitely makes sense, ill definitely give it a try
so in the page.jsx it would be something like
'use client'
import ResourceContainer from "@/components/Card/ResourceContainer";
import { useSearchParams } from "next/navigation";
export default function Home() {
const searchParams = useSearchParams()
const category = searchParams.get('category')
return (
<main>
<ResourceContainer category= {category} />
</main>
);
}close, and yeah that would work but now you're making the page a client component when it doesn't need to be, it can stay a server component since pages recieve searchParams as a prop.
It would like this:
It would like this:
import ResourceContainer from "@/components/Card/ResourceContainer";
export default function Home({ searchParams }) {
const category = searchParams.get('category')
return (
<main>
<ResourceContainer category={ category } />
</main>
);
}Sorry it would actually look like this:
import ResourceContainer from "@/components/Card/ResourceContainer";
export default function Home({ searchParams }) {
const { category } = searchParams
return (
<main>
<ResourceContainer category={ category } />
</main>
);
}you have to either destructure the param or just access it like
searchParams.category since it returns a plain JS object and not a URLSearchParams instance.I prefer to destructure, but, it's personally preference.
Hygen HoundOP
ahhh I see
all these stuff are destroying my brain haha
so then when passing the category down to the ResourceContainer, would I use the useRouter to read the searchParams passed from the page.js?
@Hygen Hound all these stuff are destroying my brain haha
Yeah app router is a huge mental switch for anyone new to it, definitely takes a bit to understand since it has so many new APIs and such that you need to wrap your head around.
@Hygen Hound so then when passing the category down to the ResourceContainer, would I use the useRouter to read the searchParams passed from the page.js?
no you just passed it down as a prop, so you can just read it from the component props
export async function ResourceContainer({ category }) {} Hygen HoundOP
yess alright, it starts to click a bit now
thank you so much
ill have a play around with the code
Yeah accessing the searchParams is Next.js everything after is just plain ol' React
Hygen HoundOP
hahahah awesome man
how long did it take you to adapt to the app directory?
@Hygen Hound how long did it take you to adapt to the app directory?
Well, I went from plain React to the Next.js pages router and after about a month in the pages router, app router became attractive enough for me to try in some side projects and then migrating my main application over.
I guess it depends how you define adapt, it took me about a month to have a good enough understanding to feel comfortable using it (as in anything I could previously I knew how to do in app router), but, they are still things I am learning today about it. I've been using App Router since around Febuary of this year.
I guess it depends how you define adapt, it took me about a month to have a good enough understanding to feel comfortable using it (as in anything I could previously I knew how to do in app router), but, they are still things I am learning today about it. I've been using App Router since around Febuary of this year.
I'd say the trickest part of the app router for me currently (and probably for most) is the cache invalidation in Next.js. Other than that I feel pretty confident.
Hygen HoundOP
Mmm yea that's super cool
I just moved to Next.js from plain React just like a week ago or so
Yeah I think moving from plain React to Next.js App Router overall is an easier transition than moving from plain React to Next.js Pages Router.
The app router has less Next.js specific abstractions and more of improving/extending the core React features which I absolutely love.
The app router has less Next.js specific abstractions and more of improving/extending the core React features which I absolutely love.
Of course the React Server Component paradigm shift is massive though and definitely a lot to re-consider
Hygen HoundOP
Yes I totally agree haha, A lot of Next.js features are really awesome, especially the SEO features + performance capabilities
yep stepping into the server component stuff is super fresh for me
It is for everyone, Server Components have only been stable for a few months
Yeah Next.js is in a really good spot right now, and the improvements they've announced are coming like PPR (Partial Pre-Rendering) and an easier way to interact with the caching behaviors in Next.js with
unstable_cache & unstable_noStore coming will help as well. I don't have a single complaint about Next besides how aggressive the caching is (which is good in most cases) but the invalidation of that cache isn't very intuitive, but, as I said those above functions will help out with that.Peterbald
Hi @Hygen Hound @Plague
I also like the Next.js. Next.js is the best framework among javascript library I think.
It can implement SSR and PPR and also ISR. There are several ways to render in Next.js project.
And since released Next.js@13, they can support App routing also. It is innovative thing as well.
I also like the Next.js. Next.js is the best framework among javascript library I think.
It can implement SSR and PPR and also ISR. There are several ways to render in Next.js project.
And since released Next.js@13, they can support App routing also. It is innovative thing as well.
@Plague Yeah Next.js is in a really good spot right now, and the improvements they've announced are coming like PPR (Partial Pre-Rendering) and an easier way to interact with the caching behaviors in Next.js with `unstable_cache` & `unstable_noStore` coming will help as well. I don't have a single complaint about Next besides how aggressive the caching is (which is good in most cases) but the invalidation of that cache isn't very intuitive, but, as I said those above functions will help out with that.
Hygen HoundOP
I definitely should read more on this hahah I have no idea what these concepts are. Ill take a brain breather and come back to implementing the searchParams 

@Hygen Hound I definitely should read more on this hahah I have no idea what these concepts are. Ill take a brain breather and come back to implementing the searchParams <:lolsob:753870958489632819>
LMAO yeah sorry I forgot you literally just got into Next.js, yeah focus on implementing what you want to implement then you can look around all the cool stuff Next also has 

Hygen HoundOP
Haha yes, ill be on the grind
Hygen HoundOP
@Plague also, I am back with where we left off. Might be a redundant question but, once I accept the category prop in the ResourceContainer, what can I do with it? It's somewhat difficult for me to wrap my head around the params stuff. Should I dynamically render the content stuff in its own component so that I can use useSearchParams() in the ResourceContainer as a client component and then pass down the variable down to the Filtering button component?
export default async function ResourceContainer({ categories }) {
const resources = await fetchContentful();
const categoryCount = {};
// Iterate over resources and update categoryCount
resources.forEach((resource) => {
const categoryItem = resource.fields.category.fields.category;
categoryCount[categoryItem] = (categoryCount[categoryItem] || 0) + 1;
});
// Filter resources based on the selected category
const filteredResources = selectedCategory
? resources.filter(
(resource) =>
resource.fields.category.fields.category === selectedCategory
)
: resources;
return (
<section>
<div>
{/* Filtering button, mapping the buttons with contentful */}
{Object.entries(categoryCount).map(([category, count]) => {
return (
<Link
key={category}
href={`/?category=${categories}`}
>
<span>{category}</span>
<span>{count}</span>
</Link>
);
})}
</div>
<div>
{filteredResources.map((resource) => (
<ResourceCard key={resource.sys.id} resource={resource} />
))}
</div>
</section>
);
}Note: I changed the prop name to categories instead of category since it clashes with the category parameter in the mapped function
@Hygen Hound <@683517071749021779> also, I am back with where we left off. Might be a redundant question but, once I accept the category prop in the ResourceContainer, what can I do with it? It's somewhat difficult for me to wrap my head around the params stuff. Should I dynamically render the content stuff in its own component so that I can use useSearchParams() in the ResourceContainer as a client component and then pass down the variable down to the Filtering button component?
You can do whatever you want with that category, in your case you're using it to filter your data right? all that category searchParam represents is the value after
?category=.for example:
if the URL is
then
if the URL is
https://website.com/?category=latestthen
const { category } = searchParams category in this case will be equal to "latest"Hygen HoundOP
ahh I see
I'm still a bit lost haha, in plain react I remember having to use useSearchParams to get the query and use setSearchParams setter function to set the search params
would I need to import the useSearchParams in the ResourceContainer component to get the query?
I also took a look at the documentation on updating useSearchParams as well but not too sure what's going on 🥲. it seems that they're doing this in a client component to implement the sorting
Hygen HoundOP
This is quite the exact same thing that I would like to achieve with the filtering
https://www.freelancethings.co/
https://www.freelancethings.co/
@Hygen Hound I'm still a bit lost haha, in plain react I remember having to use useSearchParams to get the query and use setSearchParams setter function to set the search params
yeah you would have to do that in Next as well ONLY in client components, afaik your components are server components, so you can have an even easier time with the params, it's just a new way of doing it.
I achieve the same type of filtering by grabbing the category and page (I'm using pagination) from the params, validating them, and passing them to a contentful fetch query which displays the filtered data.
Hygen HoundOP
ohhh I see
@Plague I achieve the same type of filtering by grabbing the category and page (I'm using pagination) from the params, validating them, and passing them to a contentful fetch query which displays the filtered data.
Hygen HoundOP
is there an example code that would represent this? I think I will definitely have a better time to digest and understand how it works haha
I'll show you mine, bear in mind that I'm using Dynamic Route params instead of searchParams for caching/pre-rendering reasons, but, pretend that
params in this case are searchParams it would work the same way.export default async function CategoryPage({ params }: Props) {
const { categories, category, page, totalPages } = await validateNewsRoutes(params.category, params.page)
const skip = page <= 1 ? 0 : (NEWS_LIMIT * page) - NEWS_LIMIT
return (
<>
<NewsSelector category={ category } categories={ categories } currentPage={ page } />
<Suspense fallback={<NewsLoader />}>
<NewsGrid category={ category } limit={ NEWS_LIMIT } skip={ skip } />
</Suspense>
<NewsButtons totalPages={ totalPages } category={ category } currentPage={ page } />
</>
)
}also I'm using TypeScript so ignore any non JS stuff lol
I'm taking the params from the page, just like you would, in my case I'm passing them to a validate function that makes sure the params are things I can work with and if not assigning them to things I can work with.
Then I pass that category and other things to my
Then I pass that category and other things to my
NewsGrid component, where I make the contentful fetch using that category and map through the Posts in there.export default async function NewsGrid({ category, limit, skip }: Props) {
const newsPosts = await getPosts<TypeNewsSkeleton>({
content_type: 'news',
order: ['-fields.date'],
'fields.category.sys.contentType.sys.id': 'newsCategory',
'fields.category.fields.slug[match]': category === 'latest' ? null : category,
skip: skip,
limit: limit
})
return (
<section className='grid grid-cols-news w-4/5 mx-auto justify-center items-center gap-12 px-4 pb-4'>
{ newsPosts.items.map(post => (
<NewsCard key={ post.sys.id } post={ post } />
))}
</section>
)
}also
getPosts in this case is my custom wrapper around client.getEntries() so that I can get TypeSafe queries and re-use around my application as needed.Hygen HoundOP
This is very much helpful again, i guess for my case i wouldnt need to validate my params right?
Also, just out of curiosity how did you also implement your NewsSelector as well?
@Hygen Hound This is very much helpful again, i guess for my case i wouldnt need to validate my params right?
You still should in my opinion. It's not as important since you're not basing the amount to fetch on params, but, you're still basing a fetch off those params so you should validate it to make sure it doesn't result in a wasted API request.
export default function NewsSelector({ category, categories, currentPage }: Props) {
const router = useRouter()
const sortedOptions = ['latest', ...categories].filter(option => option !== category)
const onChangeHandler = (option: string) => {
categories.forEach(category => {
if (category === option) return
else router.push(`/${NEWS_ROUTE}/${option}/${currentPage}`)
})
}
return (
<section className='relative top-0 -left-2 w-full md:max-w-[65.2%] mx-auto mt-8 z-10 h-20 p-4 animate-fadeIn font-orbitron'>
<Select onValueChange={ value => onChangeHandler(value.toLowerCase()) }>
<SelectTrigger className='capitalize max-w-[180px] bg-primary text-secondary border-secondary font-bold'>
<SelectValue placeholder={ category.replace('-', ' ') } />
</SelectTrigger>
<SelectContent className='capitalize bg-primary border-secondary'>
<SelectGroup>
{ sortedOptions.map((option, index) => (
<SelectItem key={ `${option}_${index}` } value={ option } className='text-gray-100 focus:bg-secondary font-medium'>
{ option.replace('-', ' ') }
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</section>
)
}All I'm doing in my
NewsSelector is creating an array of categories that do not include the currently selected one, maping over those and displaying them in a select box, and the currently selected category is the current value of the select box, and when someone clicks a new category I use router.push() (remember to import this route from next/navigation and not next/router) to set the send the user to the "new" route that I can pull category and page out of the URL to update my fetch. In your case, you would be pushing with searchParams like so /?category=YOUR_VALUE_HEREHygen HoundOP
Yes 👠it would be /?category=VALUE
Hygen HoundOP
@Plague I think i'm sooo close to getting the feature implemented all thanks to you man
just final stretch and I might need your support again haha
right now I got the filtering tabs to work and push the queries in the URL
but i'm not too sure how to filter and display the cards accordingly to the category that is selected with contentful and such
Here is my ResourceContainer.jsx
// Library
import { createClient } from "contentful";
// Components
import ResourceCard from "@/components/Card/ResourceCard";
import Tab from "../TabNavigation/Tab";
async function fetchContentful() {
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_KEY,
});
const res = await client.getEntries({
content_type: "resourcesPage",
include: 2,
order: "fields.category.sys.id"
});
return res.items;
}
export default async function ResourceContainer({ category }) {
const resources = await fetchContentful();
return (
<section>
<Tab resources={resources} />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
{resources.map((resource) => (
<ResourceCard key={resource.sys.id} resource={resource} />
))}
</div>
</section>
);
}to update you with the new change: I have extracted my filtering buttons into a component so I can pass in the contentful categories data in the ResourceContainer and use client routing features in the Tab component
I'm not too sure but I noticed that you used the order key in the contentful to filter the card accordingly as well 

@Hygen Hound I'm not too sure but I noticed that you used the order key in the contentful to filter the card accordingly as well <:thinq:1158595784938365018>
Yup, that's how you should sort your data response
@Hygen Hound but i'm not too sure how to filter and display the cards accordingly to the category that is selected with contentful and such
Depends on how your categories are defined in your Content Types on Contentful.
Hygen HoundOP
hmmm would it help if I were to show the json for the content type?
@Hygen Hound hmmm would it help if I were to show the json for the content type?
Do you have a seperate content type for the categories and are referencing them in your Resource content type or how are you relating them inside the CMS?
Hygen HoundOP
Yep I have a separate content type for the categories and then referencing the categories in the resource content
Yeah so then you would do something as simple as what I showed up here in the
getPosts function https://nextjs-forum.com/post/1170684712633770034#message-1172710789250039891Specifically these two lines:
'fields.category.sys.contentType.sys.id': 'newsCategory',
'fields.category.fields.slug[match]': category === 'latest' ? null : category,'newsCategory' is whatever the ID of your category content type is and the string under that is a simple matcher.
Hygen HoundOP
ohhh okay, i'll play around with this
thanks man!
absolute goat man 

it's finally working
one last problem right now haha every time I filter the category the category buttons are also filtered
hmmm
let me see your filter buttons component that you made
Hygen HoundOP
here you go:
"use client"
// Note: This component is used to filter resources by category
import { useRouter } from "next/navigation";
export default function TabButtons({resources}) {
const router = useRouter();
const categoryCount = {};
// Iterate over resources and update categoryCount
resources.forEach((resource) => {
const categoryItem = resource.fields.category.fields.category;
categoryCount[categoryItem] = (categoryCount[categoryItem] || 0) + 1;
});
const categories = Object.keys(categoryCount);
const onChangeHandler = (e) => {
categories.forEach((category) => {
if (e.target.innerText === category) {
router.push(`/?category=${category}`, {scroll: false});
}
})
}
return (
<div className="flex mb-8 justify-center">
{/* Filtering button */}
{Object.entries(categoryCount).map(([item, count]) => {
return (
<button
key={item}
onClick={(e) => onChangeHandler(e)}
className=" py-1 px-4 flex gap-x-1 font-medium border border-dim-gray rounded-full hover:border-text transition-all active:text-dark-charcoal active:bg-accent"
>
<span className=" text-sm ">{item}</span>
<span className="flex justify-center items-center text-text text-xxs w-4 h-4 bg-super-dark-gray rounded-full leading-none">
{count}
</span>
</button>
);
})}
</div>
);
}I think it makes sense since i'm taking the resources data from the ResourceContainer so every time I filter the buttons will be filtered as well
Yeah if the behavior is expected then seems fine, can you give me an example of whats happening and what you expect?
Cause if you're clicking on say "latest" and then the filtering buttons change and remove "latest" as one of the options, then that means you're coding it like a select/combo box instead of a just filter buttons.
Cause if you're clicking on say "latest" and then the filtering buttons change and remove "latest" as one of the options, then that means you're coding it like a select/combo box instead of a just filter buttons.
Hygen HoundOP
yep for sure
Here's the filtering right now
Whereas it would be ideal to have all the filtering buttons stay when a category is selected
Where are you filtering the resources, in the parent component of the ResourceContainer?
Hygen HoundOP
I'm currently filtering it in the ResourceContainer
I'm not seeing that here: https://nextjs-forum.com/post/1170684712633770034#message-1173868973897552002 Did you change some code?
Hygen HoundOP
I just updated the order of the contentful to:
const res = await client.getEntries({
content_type: "resourcesPage",
include: 2,
order: ['-fields.publishedDate'],
'fields.category.sys.contentType.sys.id': "categories",
'fields.category.fields.category': category === 'all' ? null : category,
});Okay yup, so the category data that gets passed to the Tab component needs to be an independent fetch from the filtering. You'll need two data fetches, one to get the categories, and one to filter the resources by the current category. It's unfortunately a neccessary thing (I hate it), but, it's the only way to get the correct behavior, and it won't be that big of a deal once
unstable_cache becomes stable.I'd suggest lifting the wrapping <section> tag and Tab component up to the parent, fetching the categories in there, passing that data to the Tab component, and then wrapping the ResourceContainer in a suspense boundary, and making the filtering fetch inside there, that way you have the correct data that each component needs.
Essesntially, your page will look like this:
export async function Page({ searchParams }){
const { category } = searchParams
const categories = fetchContentfulCateogries()
return (
<whatever tags you have here>
<section>
<Tab cateogories={ categories } />
<Suspense fallback={<Loading />}>
<ResourceContainer />
</Suspense>
</section>
</whatever tags you have here>
)
}this is just like a visual example, you can fill in whatever your page needs.
Hygen HoundOP
ohhhh I see
except my fetch happens inside that
validateNewsRoute function since I need to validate my params before using them for data fetches.Hygen HoundOP
yep makes sense
wow thank you so much man, I'm beyond words on your dedication to teach me all these stuff
@Hygen Hound wow thank you so much man, I'm beyond words on your dedication to teach me all these stuff
No problem man, I had some great people show me a lot of what I know today, so I like to reciprocate that on places where I'm knowledgeable enough to contribute. Luckily, I have a lot of experience with contentful 

Hygen HoundOP
Yea it's amazing, I can totally resonate with reciprocating on what we've learned. you're definitely one of the great ones in the community for sure. I don't think I have seen a thread this long 😂
is it alright if I send you a friend request? I'd love to show you the final application once it's finished
Yeah I accepted it. I'd love to see the final product for sure.
Hygen HoundOP
haha I'm happy to hear that, I will keep you in the loop 
