Conditional filtering in Next.js function for fetching posts

3 replies
Last updated: Jun 26, 2024
I have a function in Next JS for fetching posts.

export async function getPosts(start: number = 0, end: number = 9, _id?: string) {
	const getPostsQuery = groq`
		// Get posts         That are in the selected category   And order them by published date
		*[_type == "post" && $categoryId in categories[]._ref] | order(publishedDate desc) {
			title,
			slug {
				current
			},
			publishedDate,
			shortDescription,
			cardImage {
				...,
				asset-> {
					...,
				}
			}
		}[$start...$end]
	`

	return client.fetch<GetPostsQueryResult>(
		getPostsQuery,
		{ start, end, categoryId: _id },
		{
			next: {
				tags: ['posts'],
				revalidate: 60,
			},
		}
	)
}
I'm conditionally passing in an "_id" depending on whether I want to fetch only posts that have a certain category. This works great for when it's passed in, but breaks when nothing is passed in.

It's specifically
$categoryId in categories[]._ref
this part of the query that is fetching based on the passed ID.
Can I make this filtering conditional, and stop it from doing this filter when nothing is passed as an ID?
AI Update

Yes, you can make that filter conditional! The select() function in GROQ is perfect for this. It works like a switch statement and lets you conditionally apply filters based on whether a parameter is defined.

Here's how to update your query:

const getPostsQuery = groq`
  *[
    _type == "post" 
    && select(
      defined($categoryId) => $categoryId in categories[]._ref,
      true
    )
  ] | order(publishedDate desc) {
    title,
    slug {
      current
    },
    publishedDate,
    shortDescription,
    cardImage {
      ...,
      asset-> {
        ...,
      }
    }
  }[$start...$end]
`

The key change is using select() with defined():

  • If $categoryId is defined (not null), it applies the filter $categoryId in categories[]._ref
  • If $categoryId is not defined, it returns true, which means the filter passes for all documents

This way, when you pass in an _id, it filters by category. When you don't pass anything (or pass undefined), it returns all posts without applying the category filter.

The select() function evaluates conditions in order and returns the value of the first condition that's true, making it ideal for conditional parameter filtering like this.

I've managed to do it by altering it to this:

export async function getPosts(start: number = 0, end: number = 9, _id?: string) {
	const getPostsQuery = groq`
		// Get posts         That are in the selected category   And order them by published date
		*[_type == "post" ${_id ? ' && $categoryId in categories[]._ref' : ''}] | order(publishedDate desc) {
			title,
			slug {
				current
			},
			publishedDate,
			shortDescription,
			cardImage {
				...,
				asset-> {
					...,
					metadata {
						...,
					}
				}
			}
		}[$start...$end]
	`

	return client.fetch<GetPostsQueryResult>(
		getPostsQuery,
		{ start, end, categoryId: _id ? _id : 'undefined' },
		{
			next: {
				tags: ['posts'],
				revalidate: 60,
			},
		}
	)
}
For me this, this is a bit of a hacky solution. Can this be done in a more "natural" way?
Template literals are the way to go here.
Do you know when the new sanity typegen will have support for this? As currently, using string interpolation isn't supported and stops types from being generated

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?