How to use GROQ to retrieve data with dynamic queries in Sanity.io

7 replies
Last updated: Mar 11, 2021
Hi all.
I have a GROQ question combined with “how would you solve it” and “possible ways to solve it”.
Tried search but couldn’t find anything on it.

I’m looking for a way to get all references and all its data down the line with one GROQ query. The thing that makes it hard for me is that I have structured it in a quite dynamic way for the editor. So I’m never sure what to expect.

This query


_*[slug.current == "_${queryParameter}"_]_

gets me the page and its fields but the interesting part is this below, it’s a content area with different blocks. To break it down menu has its reference and that reference has it references and so on.


{
  "_key": "c12345",
  "_type": "uiHero",
  "image": {
    "_type": "image",
    "alt": "deer",
    "asset": {
      "_ref": "image-png",
      "_type": "reference"
    }
  },
  "size": "medium"
}{
  "_key": "abc",
  "_type": "uiMenu",
  "description": "Här har vi en lagom beskrivning för denna menyn",
  "menu": {
    "_ref": "<tel:1234-1234-1234|1234-1234-1234>",
    "_type": "reference"
  },
  "title": "Nya menyn"
}
other examples
• page (doucment) &gt; section (object) &gt; grid (object) &gt; information-card (object)
• page (document) &gt; section (object) &gt; menu (document) &gt; menu-category (document) &gt; menu-items (document) &gt; menu-tags (document)
Is it possible to get what I want with one dynamic query or do I need to re-think? How would you do it?

Please let me know if I need to complement with schema structures.

Thanks
🙂
Mar 2, 2021, 7:41 AM
user A
Excuse my @ you. I would appreciate if you could let me know if it is possible to do what I’m trying to do, or maybe point me in the right direction.
Mar 2, 2021, 8:47 PM
Hi Zoran. You’ll know better than I will whether a complex query fits with your code better than a selection of less complex queries. The plus side is that your schemas are finite, so even deeply nested queries are possible.
I’d be looking into
conditionals —something like:

*[...] {
  condition1 => { projection },
  condition2 => { projection }
}
You might start with something like this and build onto it:


*[] {
  _type,
  _type == "post" => { title, categories[]-> },
  _type == "author" => { name }
}
Please follow up if you can’t get this working or it’s not what you’re after.
Mar 3, 2021, 4:55 PM
Hi 
user A
. Thanks for the reply. I don’t think is exactly what I’m looking for but I’m also not sure if I get it right and if my structure is right Sanities way of doing things. The problem is variations so it feels a bit “impossible” to guess all the possible queries. I base everything from “pageModular” and build it with components in “content” as you can see below. For example “uiSection” could have “uiLayout” that has references to documents and not objects. As it seems that is the limitation with groq that if it’s all objects it can get it more levels but when it’s a reference it just gets the _ref, in my situation it I cant guess a query because I don’t know what ref is, I just have a id. What I’m essentially asking for is that
*[slug.current == 'startpage']
could get all the objects and references attached to that page.
user Z
do you know if that is in the pipeline?

export default {
  name: 'pageModular',
  type: 'document',
  title: 'Sidor',
  icon: CgWebsite,
  fields: [
    {
      title: "Titel",
      name: "title",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      title: 'Slug',
      name: 'slug',
      type: 'slug',
      options: {
        source: 'title',
        slugify: (input) => input.toLowerCase().replace(/\s+/g, "-").slice(0, 200)
      }
    },
    {
      title: 'Innehåll',
      name: 'content',
      type: 'array',
      of: [
        { type: 'uiEventList' },
        { type: 'uiHero' },
        { type: 'uiLayout' },
        { type: 'uiMenu' },
        { type: 'uiSection' },
      ]
    }
  ]
}

export default {
  name: 'uiMenu',
  type: 'object',
  title: 'Meny',
  fields: [
    {
      title: "Titel",
      name: "title",
      type: "string"
    },
    {
      title: "Beskrivning",
      name: "description",
      type: "string"
    },
    {
      title: 'Välj meny',
      name: 'menu',
      type: 'reference',
      to: [
        {
          type: 'menuCollection'
        }
      ]
    }
  ]
}

export default {
  name: 'menuCollection',
  type: 'document',
  title: 'Menyer',
  icon: BiFoodMenu,
  fields: [
    {
      title: "Titel",
      name: "title",
      type: "string",
      validation: (Rule) => Rule.required(),
    },
    {
      title: "Beskrivning",
      name: "description",
      type: "text",
      validation: (Rule) => Rule.required(),
    },
    {
      title: "Aktiv",
      name: "isActive",
      type: "boolean",
    },
    {
      title: 'Kategorier',
      name: 'categories',
      type: 'array',
      of: [
        {
          type: 'reference',
          to: [{type: 'menuCategory'}]
        }
      ]
    }
  ]
}
Mar 10, 2021, 2:49 PM
Hi Zoran. Thanks for the details to help me understand. For what you posted above, I’d imagine you could return all your data with something like:

*[_type == "document"] {
  ...,
  content[] {
    ...,
    uiMenu {
      ...
      menu-> {
        ...,
        categories[]->
      },
    },
  }
}
I tend to try to create different queries for each situation in which I’m pulling back data. For a query on a different page that’s asking for different data, I would probably create a new query rather than trying to build it into this one using
select()
or outer joins . Sometimes two queries might differ by something really simple, like the number of documents returned (adding
[0..2]
to the end of the query, for example), but that can be handled using concatenation so your code stays dry.
I hope this is on the right path to get you where you want to go. Please feel free to post back if not.
🙌
Mar 10, 2021, 3:14 PM
We've discussed a "recursive reference expansion" feature internally and haven't completely ruled out that we might add it later, but it's a pretty big footgun both for you and for us. It can quite quickly lead to fetching way more data than you actually need, which is bad for performance. There's also things like recursion; document A references document B which references document A. This would in theory lead to an infinitely deep expansion, which we'd have to either fail on, or stop at a certain depth, both of which are less than ideal
Mar 10, 2021, 11:03 PM
Having said that, GROQ isn't too different from GraphQL in this regard; you can do conditional projections as Geoff outlined earlier. The queries might get a little verbose, but at least you can prevent overfetching 😉

*[slug.current == $someSlug][0] {
  title,
  content[] {
    _type == 'uiMenu' => {
      title,
      description,
      menu[]->{
        title,
        description,
        isActive,
        categories[]->{
          // ... dunno which properties exist on "menuCategory"
        }
      }
    },
    _type == 'uiEventList' => {
      // ... dunno which props exists here
    },
    _type == 'uiHero' => {
      // ... dunno which props exists here
    },
  }
}
Mar 10, 2021, 11:08 PM
user Z
user A
big thanks to you both off you, the answers got the ball in motion for me regarding how to work with GROQ. Thanks Espen for the explanation regarding the recursive.
Mar 11, 2021, 7:35 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?