@nrk/nrkno-iframe-preview-api

Framework-agnostic library for interacting with [IFramePreview](https://github.com/nrkno/nrkno-sanity-libs/tree/master/packages/sanity-plugin-nrkno-iframe-preview#nrksanity-plugin-nrkno-iframe-preview) in Sanity Studio.

Usage no npm install needed!

<script type="module">
  import nrkNrknoIframePreviewApi from 'https://cdn.skypack.dev/@nrk/nrkno-iframe-preview-api';
</script>

README

@nrk/nrkno-iframe-preview-api

Framework-agnostic library for interacting with IFramePreview in Sanity Studio.

initPreview should be used by the app rendered inside the iframe.

See docs in @nrk/sanity-plugin-nrkno-iframe-preview for a detailed description of how this library works.

preview.png Figure 1: Sanity form editor in the left pane, IFramePreview showing iframe content in the right pane.

Installation

npm install --save @nrk/nrkno-iframe-preview-api

Usage

Refer to jsdocs for each config-param for details.

For any issues, see "Usage checklist" and "Debugging issues" below.

Basic usage

import { initPreview } from '@nrk/nrkno-iframe-preview-api'

// Somewhere after page load
const unloadPreview = initPreview<QueryResult | undefined>(
    {
      // _rev field is REQUIRED. See note about _rev field below.
      // query passed as the first param to sanityClient.fetch
      groqQuery: "* [_type='my-page' && _id == $id]{_rev, ...}[0]",
      
      // query params are passed as the second param to sanityClient.fetch,
      // and can be used in the groqQuery (ie $id) 
      queryParams: (doc) => ({id: doc._id}), // default
    },
    (data) => {
      if (!data) {
        //we will end up in here if the query has no data and
        //during inital load becuase initialData param is undefined.
        return;
      }
      // When this page is loaded by IFramePreview in Sanity Studio,
      // this callback will receive updated query-data whenever the Studio makes edits.
      // Use it to update your page in whatever way makes sense.
      
      // setData can be async
    }
)


// when the component that uses the preview is unmounted, ensure to call
unloadPreview()

Usage Checklist

Ensure that:

  • your query explicitly contains _rev as a projected field. For details, see "About the _rev field".
  • queryParams function is invoked, and that you are returning an object with all query parameters used by your query.
    • Example query: * [slug=$slug]{_rev, _id, slug, title, body }[0]
    • Example queryParams: doc => {slug: doc.slug}
    • Note: the doc passed to queryParams is active document in Sanity Studio, not the one returned by the groqQuery
  • the setData function handles undefined values.
  • the setData function is receiving data, is updating your view and not is throwing an error.

Use debug: true for diagnostics (see below). Use it to find the query and params used for each update by the Studio to debug parameterized queries (for instance, by running them in Vision).

Advanced usage

import { initPreview } from '@nrk/nrkno-iframe-preview-api'

// assume the page is server side rendered, and prepobulated with data
const prefetchedData = {_rev: '123', _id: '2'}

// Somewhere after page load
const unloadPreview = initPreview<QueryResult | undefined>(
    {
      sanityClientVersion: "2021-06-01",
      groqQuery: "* [_type='my-page' && slug._current == $slug]{_rev, ...}[0]",
      queryParams: (doc) => ({slug: doc.slug._current}),
      initialData: prefetchedData,
      origin: 'https://my-sanity-studio.example'
    },
    (data) => {
      if (!data) {
        return;
      }
      // update app
    }
)


// whenever the component that uses the preview is unmounted, ensure to call
unloadPreview()

Debugging issues

Provide debug: true as a config parameter. The log messages in console.log should provide a trace of any issues.

initPreview<QueryResult | undefined>(
    {
      // This flag will make the lib log messages being passed between Sanity Studio and the iframe 
      debug: true,
      // debug: process.env.NODE_ENV !== production
      groqQuery: "* [_type='my-page' && _id == $id]{_rev, ...}[0]",
    },
    (data) => {/* omitted */ }
)

Use Studio document as is

Omit groqQuery param completly.

Now data will always be the document used by the studio (possibly passed through by mapDocument in IFramePreview).

import { initPreview } from '@nrk/nrkno-iframe-preview-api'

// Somewhere after page load
const unloadPreview = initPreview<QueryResult | undefined>({},
    (data) => {
      if (!data) {
        return;
      }
      // data will be what the Sanity studio has; no groq queries will be execuded.
    }
)

// when the component that uses the preview is unmounted, ensure to call
unloadPreview()

Use with React

import React, {useState, useEffect} from 'react'
import { initPreview } from '@nrk/nrkno-iframe-preview-api'

export function MyApp(props: {initialData: QueryResult, enabledPreview: boolean}) {
  const [appState, setAppState] = useState<QueryResult>(props.initialData)
  
  useEffect(() => {
    if (!props.enabledPreview) {
      return 
    }
    // cleanup function is returned to React
    return initPreview<QueryResult | undefined>(
      {
        groqQuery: "* [_type='my-page' && _id == $id]{_rev, ...}[0]",
        queryParams: (doc) => ({id: doc._id}),
        initialData
      },
      (data) => {
        if (!data) {
          return;
        }
        setAppState(data)
      }
    );
  }, [] /* only run effect on mount */)
  
  // updates whenever we get data from Sanity studio
  return <div>{JSON.stringify(appState)}</div>
}

About the _rev field

All Sanity documents contains a _rev field. It changes everytime the document changes, and is unique.

For Sanity Studio to know when the groq-query returns up-to-date data, IFramePreview will compare the _rev field in the current studio document, with the _rev field in the query.

Therefore the groqQuery MUST contain the _rev-field projected at the top level, and it should correspond to the _rev field of the document edited in the studio.

It has to be explicitly projected, so it can be validated without running the query. Queries without projections can be easily rewritten like this:

  • Query without projection and _rev field: *[_id == $id][0]
  • Query WITH projection and _rev field: *[_id == $id][0]{_rev, ...}

If the query contains no _rev an error will be thrown.

Sequence diagram for dataflow

sequence.png Figure 2: Sequence diagram for dataflow between Sanity Studio and the iframe

Develop

Test

In this directory

npm run build
npm link
cd /path/to/your/render/app
npm link  @nrk/sanity-plugin-nrkno-iframe-preview

If you use volta, you must ensure that your render app also uses volta, otherwise npm link will not work correctly.

Remember to use localhost url in the Studio to test the IFramePreview component with the app using the linked lib.