Query posts by category reference - accessing nested post titles

23 replies
Last updated: Jan 12, 2026
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.

Show original thread
23 replies

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?