Skip to content

Instantly share code, notes, and snippets.

@hdoro
Created September 17, 2018 22:43
Show Gist options
  • Save hdoro/fc02049d78c41dee45eebfa0a0e8e908 to your computer and use it in GitHub Desktop.
Save hdoro/fc02049d78c41dee45eebfa0a0e8e908 to your computer and use it in GitHub Desktop.
My implementation of fetching data from Sanity for live-previewing on a Gatsby site. Extension of my guide on https://henrique.codes

Fetching and subscribing to data from Sanity

const sanityClient = require('@sanity/client');
const imageUrlBuilder = require('@sanity/image-url');

// I store my query in another file for reusability with
// the `gatsby-source-plugin`
const modularQueries = require('../../../../sanityQueries/modularQuery.js');
import { removeWhitespace } from '../../../utils/strings';
import {  normalizeSanityData } from './normalize';

let timeToFetchAfterMutation: any;

export const subscribeToData = (
  pageId: string,
  callback: () => any
) => {
  const client = sanityClient({
    projectId: process.env.SANITY_ID,
    dataset: process.env.SANITY_DATASET,
    useCdn: true,
    withCredentials: true,
  });
  const query = `
  *[_id == 'drafts.${pageId}']{
    ${removeWhitespace(modularQueries.modularQueryBody)}
  }`;
  client
    .listen(query, {}, {includeResult: false})
    .subscribe(() => {
      clearTimeout(timeToFetchAfterMutation);
      timeToFetchAfterMutation = setTimeout(() => {
        callback();
      }, 500);
    });
};

export const fetchDataFromSanity = async (
  pageId: string,
  isDraft: boolean = false
) => {
  const client = sanityClient({
    projectId: process.env.SANITY_ID,
    dataset: process.env.SANITY_DATASET,
    useCdn: true,
    // If it's a draft, we want to use credentials to
    // access non-published documents. In order for this
    // to work, you'll have to whitelist your domain!
    withCredentials: isDraft,
  });
  const query = `
  *[_id == '${isDraft ? `drafts.${pageId}` : pageId}']{
    ${removeWhitespace(modularQueries.modularQueryBody)}
  }`;
  const crudeData = await client.fetch(query);
  if (crudeData && crudeData[0]) {
    const builder = imageUrlBuilder(client);
    const getImageURL = (image: any) => builder.image(image).url();
    const normalizedData = await normalizeSanityData({
      data: crudeData[0],
      getImageURL,
    });
    if (normalizedData) {
      return normalizedData;
    }
  } else {
    return {
      data: undefined,
      getImageURL: undefined,
    };
  }
};

Normalizing the data

The only thing I do when normalizing the data is storing an imageUrl property to image objects in the data in order for them to show up in the front-end. All my image components are prepared to receive this ;)

export type TGetImageURL = (image: any) => any;

export interface ISanityDataGetImage {
  getImageURL?: TGetImageURL;
  data?: any;
}

const isImagelessObject = (field: any) => typeof field == 'object'
  && field._type !== 'image';

const isImage = (field: any) => {
  return typeof field == 'object' &&
    field._type &&
    field._type === 'image' &&
    field.asset;
}

const saveImage = async (field: any, getImageURL: TGetImageURL) => {
  // Build the URL for the image using Sanity's package
  const imageUrl = await getImageURL(field);
  let newField = {...field};
  if (imageUrl) {
    newField = {
      ...field,
      imageUrl,
    }
  } else {
    console.error(`Erro em salvar uma imagem.`);
  }

  return newField;
};

const analyzeField = async (field: any, getImageURL: TGetImageURL) => {
  let finalField = field;
  for (const key of Object.keys(field)) {
    let newField = field[key];
    if (isImagelessObject(field[key])) {
      // if it's an object without an image, we want to go deeper
      // into its structure to check for images there
      newField = await analyzeField(newField, getImageURL);
    } else if (isImage(field[key])) {
      // If it's an image field with an asset, save the image
      newField = await saveImage(newField, getImageURL);
    } else {
      // If not an object, we simply skip this key
      continue
    }

    // swap out the previous field with the new one
    finalField = Object.assign(finalField, {
      [key]: newField,
    })
  }
  return finalField;
}

export const normalizeSanityData = ({data, getImageURL}: ISanityDataGetImage) => {
  if (data && getImageURL) {
    return analyzeField(data, getImageURL);
  } else {
    return undefined;
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment