👀 Our most exciting product launch yet 🚀 Join us May 8th for Sanity Connect

Improving performance by filtering GROQ queries into separate arrays by _type.

20 replies
Last updated: May 18, 2023
I’d like to put my results into separate arrays by _type. How would I do that? Current GROQ is:
[_type in ["product", "set", "collection", "room", "post"] && [store.title, title] match "${params.s}"] {
    _type == 'post' => {_id, _type, 'image': image.asset->url, title, 'slug': slug.current},
    _type == 'room' => {_id, _type, 'image': image.asset->url, title, 'slug': slug.current},
    _type == 'collection' => {_id, _type, 'image': image.asset->url, title, 'slug': slug.current},
    _type == 'set' => {_id, _type, 'image': image.asset->url, title, 'slug': slug.current}, 
    _type == 'product' => {_type, primary->, 'shopifyId': store.id, 'image': store.previewImageUrl, 'slug': store.slug.current, 'title': store.title }}
May 16, 2023, 11:24 PM
It may be more performant to wrap your queries in an object instead of using a conditional within a single projection. Before I put together an example, it looks like all types have the same project accept for a product. Is that right?
May 16, 2023, 11:41 PM
yes
May 16, 2023, 11:41 PM
I have noticed this query takes longer
May 16, 2023, 11:42 PM
Ok cool, I’d do something like this:
const query = {};

['product', 'set', 'collection', 'room', 'post'].forEach(type =>
  Object.assign(
    query,
    type !== 'product'
      ? {
          [type]: `*[_type == '${type}' && [store.title, title] match $s]{_id, _type, 'image': image.asset->url, title, 'slug': slug.current}`,
        }
      : {
          product: `*[_type == 'product'] {_type, primary->, 'shopifyId': store.id, 'image': store.previewImageUrl, 'slug': store.slug.current, 'title': store.title }]`,
        }
  )
);

await client.fetch(query, {s})
Filtering this way is much faster than using conditionals in a project. You also don’t have to repeat the projection a bunch of times inside of your code.
May 17, 2023, 12:03 AM
I’m not using the client
May 17, 2023, 12:06 AM
let sanityPromise;

// Sanity API Call
async function sanityApiCall(query) {
    try {
      let response = await fetch(`<https://umt44hrc.api.sanity.io/v2022-01-01/data/query/production?query=*${query}>`)
      sanityPromise = await response.json()
      sanityPromise = sanityPromise.result
    } catch (error) {
      topBannerStart('error', error);
    }
  }

function searchSanity() {
    let query = encodeURIComponent(`[_type in ["product", "set", "collection", "room", "post"] && [store.title, title] match "${params.s}"] {${searchProjection}} | order(_type desc)`);
    sanityApiCall(query).then(res => {
        console.log(sanityPromise)
        sanityPromise.forEach((line)=>{
            listResults(line)
        });
        if(sanityPromise.length == 0) {
            let message = document.createElement('p');
            message.innerHTML = "Looks like there's nothing here!";
            message.setAttribute('class', 'default-message')
            results.append(message);
        };
    });
    
}
May 17, 2023, 12:07 AM
the search function is only for the search page. I use the sanity api call elsewhere, but not searchSanity
May 17, 2023, 12:09 AM
so do I put your code as the query in searchSanity?
May 17, 2023, 12:10 AM
because it looks like the GROQ is mixed with JS, and I’ve not seen that before
May 17, 2023, 12:10 AM
I can always mutate the result into arrays, but I’d like to make a more performant query regardless
May 17, 2023, 12:52 AM
user M
I was wondering if you had any advice for me? Thank you so much 🙏🙏🙏
May 17, 2023, 5:00 PM
Should I do separate fetches, one for each _type?
May 17, 2023, 5:01 PM
Yeah, I’m using JS here to save having to repeat the projection for each query. Try something like this:
let sanityPromise;

// Sanity API Call
async function sanityApiCall(query) {
    try {
      let response = await fetch(`<https://umt44hrc.api.sanity.io/v2022-01-01/data/query/production?query=*${query}>`)
      sanityPromise = await response.json()
      sanityPromise = sanityPromise.result
    } catch (error) {
      topBannerStart('error', error);
    }
  }

function searchSanity() {
    const query = {};

['product', 'set', 'collection', 'room', 'post'].forEach(type =>
  Object.assign(
    query,
    type !== 'product'
      ? {
          [type]: `*[_type == '${type}' && [store.title, title] match ${params.s}]{_id, _type, 'image': image.asset->url, title, 'slug': slug.current}`,
        }
      : {
          product: `*[_type == 'product'] {_type, primary->, 'shopifyId': store.id, 'image': store.previewImageUrl, 'slug': store.slug.current, 'title': store.title }]`,
        }
  )
);
    sanityApiCall(query).then(res => {
        console.log(sanityPromise)
        sanityPromise.forEach((line)=>{
            listResults(line)
        });
        if(sanityPromise.length == 0) {
            let message = document.createElement('p');
            message.innerHTML = "Looks like there's nothing here!";
            message.setAttribute('class', 'default-message')
            results.append(message);
        };
    });
    
}
May 17, 2023, 5:12 PM
You may have to tweak this though, since I’m not 100% sure where your params are coming from.
May 17, 2023, 5:12 PM
Wonderful, I'll give that a try. Afterwards I may have a question for you if I don't understand how it works.
Once again, you are an absolute GROQ magician
May 17, 2023, 5:15 PM
💜
May 17, 2023, 5:15 PM
Let me know how it goes!
May 17, 2023, 5:15 PM
Will do.
May 17, 2023, 5:15 PM
Okay, I took your code and ran with it a bit. Here is what I have now:
function searchSanity() {
    let query
    ['product', 'set', 'collection', 'room', 'post'].forEach(type => {
        switch (type) {
            case 'product': query = `[_type == 'product' && [store.title, title] match "${params.s}"] {_type, primary->, 'shopifyId': store.id, 'image': store.previewImageUrl, 'slug': store.slug.current, 'title': store.title }`
            break;
            default: query = `[_type == '${type}' && [store.title, title] match "${params.s}"]{_id, _type, 'image': image.asset->url, title, 'slug': slug.current}`
            break;
        }
        console.log(query)
        // Query sanity api for each type and list the results in div
        sanityApiCall(encodeURIComponent(query)).then(res => {
            if(res[0]) {
                console.log(res)
                div = document.getElementById(type);
                div.innerHTML = `<h4>${type}s</h4>`
                // Display each item of the given type
                res.forEach((line)=>{
                    listResults(line,type);
                });
            }
            
        }); 
    });
    
}

// Sanity API Call
async function sanityApiCall(query) {
    try {
      let response = await fetch(`<https://umt44hrc.api.sanity.io/v2022-01-01/data/query/production?query=*${query}>`)
      sanityPromise = await response.json()
      sanityPromise = sanityPromise.result
      return sanityPromise;
    } catch (error) {
      topBannerStart('error', error);
    }
  }
so each _type goes into a separate div with that ID, so I can do things like the screenshot attached!
May 18, 2023, 2:41 AM
Awesome! Glad you got it working!
May 18, 2023, 4:45 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?