Handling references in portable text in GraphQL without Gatsby Source plugin
4 replies
Last updated: Mar 10, 2023
R
In GraphQL how do you handle references in portable text e.g handling a link to an internal document? The portable text field is returned as RAW JSON with a ref. In GROQ you can grab the referenced document in the
markDefsbut can’t seem to figure out how to do it in just plain GraphQL.
Mar 9, 2023, 4:26 AM
If you’re using the Gatsby Source plugin you can automatically resolve references on your raw fields . I’m not sure if this is true outside of the plugin, though.
Mar 9, 2023, 4:29 PM
R
I am not using Gatsby 🥲
Mar 9, 2023, 6:06 PM
R
If anyone searches this in the future, here’s my solution:
import { sanityApiServer } from "@/lib/sanity"; /** * Recursively iterates over an object looking for markDefs keys and resolving internal links to Sanity references. * @template T - The type of the object being resolved. * @param {T} object - The object to resolve. * @returns {Promise<T>} - A Promise that resolves to the modified object with internal links resolved. */ export async function resolveSanityTasks<T>(object: T): Promise<T> { // Recursively iterate over the object looking for keys called "markDefs" for (const KEY in object) { if (KEY === "markDefs") { // Extract the markDefs array const MARK_DEFS = object[KEY] as any[]; if (Array.isArray(MARK_DEFS)) { /** * Loop over all the markDefs and search for internal links. * If they exist, query Sanity for the reference slugs. */ object[KEY] = (await Promise.all( MARK_DEFS.map(async (item) => { // Check if the item is an internal link with a reference slug if ( "_type" in item && item._type === "internalLink" && "reference" in item && "_ref" in item.reference && typeof item.reference._ref === "string" ) { try { // Query Sanity for the reference slug using the ID const RESPONSE = await sanityApiServer.fetch(`*[_id == "${item.reference._ref}"] { _type, slug }`); // Check that the query response is valid if ( Array.isArray(RESPONSE) && RESPONSE.length > 0 && typeof RESPONSE[0] === "object" && "_type" in RESPONSE[0] && typeof RESPONSE[0]._type === "string" && "slug" in RESPONSE[0] && typeof RESPONSE[0].slug === "object" && "current" in RESPONSE[0].slug && typeof RESPONSE[0].slug.current === "string" ) { /** * We need to capitalize the type to match * the type returned from GraphQL */ const TYPE = RESPONSE?.[0]._type; const CAPITALIZED_TYPE = TYPE.charAt(0).toUpperCase() + TYPE.slice(1); // Update the item with the reference slug and type return { ...item, __typename: CAPITALIZED_TYPE, slug: RESPONSE?.[0].slug, }; } else { // If the query response is not valid, return the original item return item; } } catch (errResponse) { // If there is an error with the query, throw an error throw new Error(); } } else { // If the item is not an internal link with a reference slug, return the original item return item; } }), )) as T[Extract<keyof T, string>]; } else { // If the markDefs array is not valid, recursively call this function on the object object[KEY] = (await resolveSanityTasks(object[KEY])) as T[Extract<keyof T, string>]; } } else if (typeof object[KEY] === "object") { // If the value of this key is an object, recursively call this function on that object object[KEY] = (await resolveSanityTasks(object[KEY])) as T[Extract<keyof T, string>]; } } // Return the modified object return object; }
Mar 10, 2023, 12:36 AM
R
And I hate it.
Mar 10, 2023, 2:12 AM
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.