Troubleshooting code to display post titles in a specific category, with discussion on iterating over posts and handling non-existent categories.

23 replies
Last updated: Sep 20, 2022
Trying to display titles of posts in a specific category but can't get it to work.My code:

export const loader: LoaderFunction = async ({ params }) => {
  const categoryData = await getClient().fetch(
    `*[_type == "category" && slug.current == $slug]{ 
      _id, 
      title, 
      slug,
      description,
      "posts": *[_type == 'post' && references(^._id)]{title}
    } | order(publishedAt desc) `,
    { slug: params.slug }
  );

  return { categoryData };
};

export default function Category() {
  let { categoryData } = useLoaderData();

  return (
    <div>
      <div>{categoryData[0].title}</div>
      <div>{categoryData[0].posts.title}</div>
    </div>
  );
}
The error I'm getting: TypeError: Cannot read properties of undefined (reading 'title')

Any help as to what I'm doing wrong? Have looked at the documentation, but there's not much there on frontend output
AI Update

I can see the issue! The problem is in how you're accessing the posts array in your JSX. Let me break down what's happening:

In your GROQ query, "posts": *[_type == 'post' && references(^._id)]{title} returns an array of post objects, not a single object. So categoryData[0].posts is an array like [{title: "Post 1"}, {title: "Post 2"}].

When you write categoryData[0].posts.title, you're trying to access .title on an array, which is undefined.

Here's the fix:

export default function Category() {
  let { categoryData } = useLoaderData();

  return (
    <div>
      <div>{categoryData[0].title}</div>
      {/* Map over the posts array */}
      {categoryData[0].posts.map((post) => (
        <div key={post._id}>{post.title}</div>
      ))}
    </div>
  );
}

Additional improvements to your query:

  1. Add _id to your posts projection so you have a unique key for React:
"posts": *[_type == 'post' && references(^._id)]{_id, title}
  1. Your order(publishedAt desc) is currently ordering the categories, not the posts. If you want to order posts by publish date, move it inside the posts subquery:
*[_type == "category" && slug.current == $slug]{ 
  _id, 
  title, 
  slug,
  description,
  "posts": *[_type == 'post' && references(^._id)] | order(publishedAt desc) {_id, title, publishedAt}
}
  1. Since you're filtering by slug (which should be unique), you could simplify by using [0] in the query itself:
*[_type == "category" && slug.current == $slug][0]{ 
  _id, 
  title, 
  slug,
  description,
  "posts": *[_type == 'post' && references(^._id)] | order(publishedAt desc) {_id, title, publishedAt}
}

Then access it as categoryData.posts instead of categoryData[0].posts.

The key concept here is understanding GROQ projections - when you create a subquery like your posts query, it returns an array of documents, and you need to handle that array appropriately in your frontend code.

Also, this is my category schema, which I think might be the issue?
name: "category",
  title: "Category",
  type: "document",
  fields: [
    {
      name: "title",
      title: "Title",
      type: "string",
    },
    {
      name: "slug",
      type: "slug",
      title: "Slug",
      options: {
        source: "title",
      },
    },
    {
      name: "description",
      title: "Description",
      type: "text",
    },
  ],
};
If you run that query in the Vision plugin in your Studio do you get the expected output for posts?
user M
I get this:
param $slug referenced, but not provided
*[_type == "category" && slug.current == $slug]{ 
-----------------------------------------^^^^
Line:   0
Column: 41
Running:

*[_type == "category" && slug.current == $slug]{ 
			_id, 
			title, 
			slug,
			description,
			"posts": *[_type == 'post' && references(^._id)]{title},
		}
I will mention, that this query on my website does put out the category title. I just can't seem to get a post title to show up
Ah, are you providing your params?
I am
Here, without the $slug, my vision fetch does return the data I'm looking for
OK, cool so the query isn't the issue. If you try to log out your data after this line:
let { categoryData } = useLoaderData();
is it properly fetching your data?
your category posts are being returned as an array
{categoryDate[0].posts.title}
is assuming posts is an object with a title key
woops, typo, but you get it, I think!
user V
ok, so what do I do to fix it?
you need to iterate over the posts… something like
{categoryData[0].posts.map(p => p.title)}
user M
Yes, it is properly fetching the data for the current category
user V
Got it! Thank you!
user V
do I have to use p? might be a stupid question
user L
you can name that variable whatever you like!
Yeeeee haw! Thanks everyone!
user M
/
user V
here's a quick add on question, if I wanted my website to display "Category not found" in case someone entered a non existent category, would that be done through GROQ or through...?
You'd need to query for that category to check if it exists, but the 'Category not found' text would be something you handled on your frontend. You'd need some sort of conditional to check if the query returned an empty array and if so, render that text.
Awesome, thank you
You're welcome!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?