Skip to content

Instantly share code, notes, and snippets.

@employee451
Last active May 7, 2024 10:17
Show Gist options
  • Save employee451/2f05d49b5d8251e2acb25fe408426b83 to your computer and use it in GitHub Desktop.
Save employee451/2f05d49b5d8251e2acb25fe408426b83 to your computer and use it in GitHub Desktop.
Resolve references in Sanity data (GraphQL)
import { groq } from 'next-sanity' // Replace with another library if you don't use Next.JS
import { client } from './client' // Replace this with wherever you have set up your client
/**
* Resolves all unresolved references when fetching from the Sanity GraphQL API.
* There is no native way to do this, so we have to do it manually.
* @param data
*/
export const resolveSanityGraphqlReferences = async <T = unknown>(
data: T
): Promise<T> => {
// If we are not dealing with an object, simply return the original value
if (typeof data !== 'object' || !data) {
return data
}
return Object.entries(data).reduce<any>(async (acc, [key, value]) => {
// If it's a reference, we need to resolve it
if (value?._type && value._type === 'reference') {
const resolvedReference = await client.fetch(
groq`*[_id == $ref][0]`,
{
ref: value._ref,
}
)
return {
...(await acc),
[key]: resolvedReference,
}
}
// If it's a nested object (not a reference), we continue the crawling
if (value?._type) {
return {
...(await acc),
[key]: await resolveSanityGraphqlReferences(value),
}
}
// For arrays, crawl every item in the array
if (Array.isArray(value)) {
return {
...(await acc),
[key]: await Promise.all(value.map(resolveSanityGraphqlReferences)),
}
}
// If it's any other value (primitive), keep the value as it is
return {
...(await acc),
[key]: value,
}
}, {})
}
@Silon
Copy link

Silon commented May 7, 2024

I created optimised version with single groq query (we had >100k requests per day because of this). It's not so beautiful but it works.

export const getResolveSanityGraphqlReferences = async <T = unknown>(data: T): Promise<T> => {
  if (typeof data !== 'object' || !data) {
    return data;
  }

  const references = {};

  const findReferences = (value, key: string) => {
    if (value?._type === 'reference') {
      references[key] = value._ref;
    }

    if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      Object.keys(value).forEach((nestedKey) => findReferences(value[nestedKey], `${key}.${nestedKey}`));
    }

    if (Array.isArray(value)) {
      value.forEach((item, index) => findReferences(item, `${key}[${index}]`));
    }
  };

  Object.keys(data).forEach((key) => findReferences(data[key], key));

  if (Object.keys(references).length === 0) {
    return data;
  }

  const resolvedReferences = await client.fetch(
    groq`*[_id in $refs]{
      _id,
      link,
    }`,
    {
      refs: Object.values(references),
    },
  );

  const resolveReferences = (value: any, key: string) => {
    if (references[key]) {
      return resolvedReferences.find((ref) => ref._id === references[key]);
    }

    if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      return Object.keys(value).reduce<any>(
        (acc, nestedKey) => ({
          ...acc,
          [nestedKey]: resolveReferences(value[nestedKey], `${key}.${nestedKey}`),
        }),
        {},
      );
    }

    if (Array.isArray(value)) {
      return value.map((item, index) => resolveReferences(item, `${key}[${index}]`));
    }

    return value;
  };

  return Object.keys(data).reduce<any>(
    (acc, key) => ({
      ...acc,
      [key]: resolveReferences(data[key], key),
    }),
    {},
  );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment