Visual Editing

Visual Editing with React Native

Get started with Sanity Visual Editing in a new or existing React Native application using the Sanity React Loader.

Following this guide will enable you to implement:

  • Live preview: See draft content updates rendered in the embedded front end in real-time.
  • Click-to-edit: Interactive overlays for the embedded front end application that help content creators find and edit the right fields.
  • Page building: Advanced capabilities for adding, moving, and removing content sections, directly from your embedded front end.
  • Preview sharing: A way for content creators to share a preview of draft content with others.
  • Locations: Shortcuts to open Presentation (the embedded front end view) for a document directly from where the content is defined the Structure tool.

Your deployed web build of the application will be loaded loaded into your deployed Sanity Studio via the Presentation plugin (and you can do the same for locally running versions of your app and your Studio, which streamlines development and debugging).

Prerequisites

  • A React Native application. Spin up your own project repo OR clone and modify the "React Native Example Repo" (see "React Native Example Repo" section)
    • Note that our examples assume use of the Expo framework because it offers useful tools for local development, creating builds for web/native/simulators, and a router package. If you'd like to use the same React Native + Expo starting point, follow this documentation to set up a new Expo project
  • A Sanity project with a hosted or self-hosted Sanity studio. (If you use the "React Native Example Repo", see the "Dependencies" subsection of the "React Native Example Repo" section).

Note that your application should use the Platform.OS functionality of the react-native library to ignore any web-specific code used for Visual Editing in the context of native builds, since Visual Editing is only used in the context of Sanity Studio (which runs in a browser). The examples below (and the "React Native Example Repo") take this into account using an isWeb util and a Sanity-provided util called isMaybePresentation.

Hosting Services and the Content Security Policy Header

A valid example Content Security Policy header is:
"frame-ancestors 'self' http://localhost:8081 https://www.sanity.io https://visual-editor-react-native.vercel.app https://rn-visual-editor.sanity.studio"

In this example, the URLs (in order) are for:

  • A development environment for the React Native app.
  • Sanity Dashboard (the centralized "content operating system" web application where deployed Studios and Sanity SDK applications are "installed" in a single organization-level view. Learn more about the Dashboard).
  • The deployed React Native app.
  • The deployed Sanity Studio.

React Native Example Repo

If you prefer to start from a working example and add your own code, we have created a starting point repo for a React Native application (built on Expo) which is ready to be loaded into the Presentation tool out of the box. Its web app builds can be deployed to Vercel and its native builds can be created via Expo build servers. This application includes:

  • The required front-end code snippets for Visual Editing (see the "Implementation in a New or Existing React Native/Expo App" section below for implementation details).
  • Example pages ("Movies", "People", etc) with layouts and routing already set up.
  • Utility components for building your own views.

The repo is fully open source - it is available on Github and has a comprehensive Readme for development and deployment.

Dependencies

As mentioned in the repo's Readme, the repo assumes you have created your Sanity Studio via the following steps (to set up content types and test data that match the front end application):

  • Run sanity init in some repo/folder (easiest/cleanest option is a separate repo, since Sanity Studio is built on vanilla React, not React Native).
  • When that init script asks you to choose a project template, choose "Movie project (schema + sample data)"
  • When the init script asks "Add a sampling of sci-fi movies to your dataset on the hosted backend?", choose "yes".

You must add configuration for the Sanity Presentation Plugin to your Sanity Studio which matches the setup for your React Native application (see "Sanity Studio Setup" section below)

Sanity Studio Setup

To setup Presentation tool in your Sanity Studio, import the tool from sanity/presentation, add it to your plugins array, and configure previewUrl and allowedOrigins as shown below.

We recommend using environment variables loaded via a .env file to support development and production environments. In the code block below, SANITY_STUDIO_REACT_NATIVE_APP_HOST is the hostname of your front end React Native application that is going to be loaded into presentation mode (either running locally or deployed, depending on the env in question).

Note that if you are using the "React Native Example Repo", you should add resolve: locationResolver to the presentationTool config (in the main config object) where locationResolver is:

This adds the functionality where "location" links are added to the top of each document in the Studio Structure view. Each of these links for a given document opens the Presentation tool and automatically loads the page where that document is used, directly in that embedded front end. The locations are defined by the resolver function (e.g. people are used both in the People Directory at /people and in their individual movie page at /movie/:movide_slug). You can add additional location resolvers for your other content types (and/or remove the movies/people location resolvers if your are no longer using those content types). See the "map content to front-end routes with locations resolver function" section in the Presentation Tool docs for examples and more info.

Add CORS Origins

Because our React Native application (and our Sanity Studio) will make client-side requests to the Sanity Studio across domains, their URLs must be added as valid CORS origins.

This can be done inside sanity.io/manage. Use the following steps for your React Native front end application and then repeat for your Sanity Studio.

  • Navigate to the API tab, then add select "Add CORS origin".
    • For local development origins, enter http://localhost:PORT where PORT is the port number that is running the application in question.
    • For deployed origins, add the full hostname of the deployed React Native application or Sanity Studio.
  • Select Save.

What about "Allow credentials"?

Only set up CORS origins for URLs where you control the code. Remember to perform the steps for each local development origin and each deployed origin for your front ends and your Sanity studio. One caveat is that deploying a Sanity-hosted studio will add the CORS config for that studio automatically. If you self-host the studio, you will need to add it yourself.

You might not need the rest of this guide

Implementation in a New or Existing React Native App:

Install dependencies

Install the dependencies that will provide your application with data fetching and Visual Editing capabilities.

npm install @sanity/client @sanity/react-loader @sanity/visual-editing @sanity/presentation-comlink

Set environment variables

Create a .env.local in your application’s root directory to provide the configuration for connecting to your Sanity data.

For platform-native builds, if you are using Expo as your build service, you will also need to create the variables in the Expo Environment Variables console for your project. For other build services, follow the appropriate environment variable specification process outlined in the documentation of the service in question.

For the web build, if you are using a hosting service where env variables are created in a browser UI (e.g. Vercel), create the variables in that UI. Other hosting services may just expect a .env file in your codebase or you might set the vars in a CI/CD pipeline, etc—this step is specific to your hosting implementation.

You can use sanity.io/manage to find your project ID and dataset.

The URL of your Sanity Studio will depend on where it is hosted or embedded.

The environment variables are below (the "EXPO_PUBLIC" prefix can be abandoned if not using Expo and/or replaced with any required prefix for your build service):

# .env.local or .env
EXPO_PUBLIC_SANITY_DATASET=Your dataset name
EXPO_PUBLIC_SANITY_PROJECT_ID=Your Sanity project ID
EXPO_PUBLIC_SANITY_STUDIO_URL=The URL of your Sanity Studio 
(running locally OR deployed, depending on env)

and import them into the runtime:

Application Setup

Add helper utilities

The isWeb utility determines if you are in the native or web context. createDataAttributeWebOnly is a helper function that helps create "data-sanity" attributes, which are used to enable click-to-edit overlays in Visual Editing/Presentation mode. These custom overlays allow you to click on any component that renders a piece of sanity content and automatically open that piece of content for editing in the visual editor's form sidebar. Many field types are automatically enabled via the "stega" configuration in the Sanity client, but some fields (e.g. images) require the data attribute to show an overlay. Overlays can also be visually customized to have a different look and feel, display more contextual information, and so on. Learn more about data attributes and custom overlays in the docs.

Add data attributes for React Native

Data Attributes in React Native

Create the data attribute using the createDataAttributeWebOnly util set up above, and apply it to the React Native component using the dataSet prop. In this example, we are adding it to a react-native Image component, but it should work for any scalar React Native component that needs to carry the "data-sanity" on its underlying html tag.

function SomeComponent({ _id, _type }) {
  const attr=createDataAttributeWebOnly({id: _id, type: _type, path: 'poster'})


  return (<WhateverParent>
          
  <Image
    // @ts-expect-error The react-native-web TS types haven't been 
    // updated to support dataSet.
    dataSet={{ sanity: attr.toString() }}
    source={{ uri: urlFor(poster).url() }}
    style={styles.image}
    />
  </WhateverParent>)
}

Repeat this for any components that need the data-sanity attributes.

Configure the Sanity Client

Create a Sanity client instance to handle fetching data from Content Lake.

As mentioned in the "Add utils" section above, the stega option enables automatic click-to-edit overlays for basic data types when preview mode is enabled. You can read more about how stega works in the docs.

Add the Sanity React Loader hooks for queries and live mode

You will fetch data in your pages/components (see below) with useQuery. This handles querying when you are not in Presentation/Visual Editing mode. For example, in the React Native mobile or web context outside the Sanity Studio.

When you enter Presentation mode in the Sanity Studio, the useLiveMode hook takes over data hydration responsibilities.

Live Content API

The Live Content API can be used to receive and render real time updates in your application without refreshing the page, both:

  • when viewing content in the Presentation tool for whatever"Perspective" is currently chosen in that Presentation UI (Draft Perspective, Published Perspective, etc).
  • when viewing published in your user-facing production application.

When you are in Presentation mode, useLiveMode will use a cookie set by the Presentation plugin to authenticate live updates from the Live Content API and show you the latest content for whatever "Perspective" you choose in the Presentation UI itself. The most common Perspective used is "Drafts", because that will show you all edits to documents, rendered in your embedded front end, live and in real time. This is how we enable instantaneous "Visual Editing". However, you can also choose the "Published" perspective to see a view of all published changes.

The useLiveMode hook respects the user's role when determining which data/content types that user can access in Presentation mode (including Custom Roles).

When you are not in Presentation mode, you must implement a connection mechanism for it in your project in order to use the Live Content API. A package is in-progress for an out-of-the-box Live Content API connector for vanilla React and React Native and will be added to this example when available.

For example/starting point implementations in the meantime, check the lcapi-examples Github Repo.

Private datasets

Private Datasets

To query private data from user-facing applications, create a private querying hook (call it usePrivateQuery or useSanityQuery or similar) that allows you to perform token-authorized queries. However, never add that token to the client side bundle/environment, it is an API KEY. Some example approaches for how to perform such queries:

  • Build an API that has custom auth (for however you authenticate your users) and returns a token for the Sanity client to use in calls to client.fetch. This is the simplest approach but has the negative side effect that it exposes the token to the client side, so any logged in user can take that token and take any action for which the token is authorized—usually at a minimum this means making ANY query to your data, but can also even include writing data, updating settings, etc depending on the token.
  • Have a proxy API that has custom auth and can make queries on your behalf from the server, which never exposes the token to client side users. This allows you to either allow arbitrary queries if all authorized users should be able to make any query or even allows you to lock down which queries can be made by exposing API routes for individual queries.

Once you have defined a private querying hook, decide at runtime whether to call the Sanity React Loader's useQuery or your own usePrivateQuery/useSanityQuery/customQuery depending on whether you are in Presentation mode. Determining whether you are likely in/not in Presentation mode can be done with a helper from @sanity/presentation-comlink called isMaybePresentation.


So an example conditional usage of the correct hook for the platform/context might be like:

const { isMaybePresentation } = import "@sanity/presentation-comlink"
const usePrivateQuery = import "@/hooks/usePrivateQuery"
    
    <!-- In a real life example, put this "createQueryStore" call in its own module so that it is called ONLY once and imported into components where used -->

    const { useLiveMode, useQuery} = createQueryStore({ client, ssr:false })

    function SomeComponent {
        const { data } = isMaybePresentation() ? useQuery(query) : usePrivateQuery()

        return <div>...contents</div>
    }

Add a SanityVisualEditing component

The enableVisualEditing function handles rendering overlays, enabling click to edit, and re-rendering elements in your application when content changes. Note that this is where we call useLiveMode. A helper component for that function is below/

If you do not use Expo Router

Render the SanityVisualEditing component

Add the SanityVisualEditing component to your root layout(s) outside of any "Stack" elements.

If you do not use Expo Router

Query data from Sanity and render a page

With the components included and loaders set up, you can call useQuery with each page's query and render the data. For example:

Components like this are where you will render the React Native components with the dataSet prop and its sanity child for clickable overlays (see the "Add data attributes for React Native" section above).

Try out visual editing

With your front end application and Sanity Studio both running locally (or deployed) and configured using the appropriate environment variables and the code snippets from this guide, you should be able to open the Studio, click "Presentation", and see your front end application embedded as a click-to-edit view with automatic live content updates.

If you don't see the page as expected, confirm the code snippets related to presentation are as expected, and take a look at the Visual Editing Troubleshooting guide.

Was this page helpful?