npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@contentful/optimization-react-native

v1.1.0

Published

React Native SDK for Contentful Optimization

Downloads

241

Readme

Guides · Reference · Contributing

The Optimization React Native SDK provides a stateful mobile runtime on top of the Optimization Core SDK. It adds React providers, hooks, OptimizedEntry, screen tracking, optional offline-aware event delivery, and in-app preview-panel support for React Native applications.

If you are integrating a React Native application, start with Getting Started, then use Integrating the Optimization React Native SDK in a React Native app for the step-by-step flow. This README keeps the package orientation and common setup options close at hand; generated reference documentation remains the source of truth for exported API signatures.

Getting started

Install using an NPM-compatible package manager, pnpm for example:

pnpm install @contentful/optimization-react-native @react-native-async-storage/async-storage

For offline support, also install NetInfo:

pnpm install @react-native-community/netinfo

For preview panel support, also install the preview-native peer dependencies:

pnpm install @react-native-clipboard/clipboard react-native-safe-area-context

Wrap the app with OptimizationRoot near the top of the component tree:

import { OptimizationRoot } from '@contentful/optimization-react-native'

export default function App() {
  return (
    <OptimizationRoot clientId="your-client-id" environment="main" locale="en-US">
      <YourApp />
    </OptimizationRoot>
  )
}

For non-component ownership paths, initialize the SDK explicitly and call methods directly:

import { ContentfulOptimization } from '@contentful/optimization-react-native'

const optimization = await ContentfulOptimization.initialize({
  clientId: 'your-client-id',
  environment: 'main',
  locale: 'en-US',
})

When to use this package

Use @contentful/optimization-react-native for React Native applications that need mobile optimization, persisted state, screen tracking, entry interaction tracking, offline behavior, and preview-panel support. Native iOS and Android package work also exists in this monorepo for teams that need platform-native integration surfaces.

Common configuration

OptimizationRoot accepts Core stateful configuration directly, plus React Native-specific props. Only clientId is required.

| Option | Required? | Default | Description | | ------------------------ | --------- | ----------------------------- | --------------------------------------------------------------------------------- | | clientId | Yes | N/A | Shared API key for Experience API and Insights API requests | | environment | No | 'main' | Contentful environment identifier | | api | No | See API options below | Experience API and Insights API endpoint and request options | | locale | No | undefined | SDK Experience API and default event locale | | contentful | No | undefined | App-provided contentful.js client for SDK-managed entry fetching | | prefetchManagedEntries | No | undefined | Managed entry descriptors to warm after the SDK is ready | | defaults | No | undefined | Initial state, commonly including consent, persistence consent, or profile values | | allowedEventTypes | No | ['identify', 'screen'] | Event types allowed before consent is explicitly set | | trackEntryInteraction | No | { views: true, taps: true } | Default view and tap tracking for OptimizedEntry components | | liveUpdates | No | false | Whether optimized entries react continuously to SDK state changes | | onStatesReady | No | undefined | Provider-managed app-level state subscription hook | | getAnonymousId | No | undefined | Function used to provide an anonymous ID from application-owned identity state | | queuePolicy | No | SDK defaults | Flush retry behavior and offline queue bounds | | logLevel | No | 'error' | Minimum log level for the default console sink | | onEventBlocked | No | undefined | Callback invoked when consent or guard logic blocks an event |

Common api options:

| Option | Required? | Default | Description | | ------------------- | --------- | ------------------------------------------ | ------------------------------------------------ | | experienceBaseUrl | No | 'https://experience.ninetailed.co/' | Base URL for the Experience API | | insightsBaseUrl | No | 'https://ingest.insights.ninetailed.co/' | Base URL for the Insights API | | enabledFeatures | No | ['ip-enrichment', 'location'] | Experience API features to apply to each request | | preflight | No | false | Aggregate a new profile state without storing it |

Common fetchOptions are fetchMethod, requestTimeout, retries, intervalTimeout, onFailedAttempt, and onRequestTimeout. Default retries intentionally apply only to HTTP 503 responses.

Choose the application Contentful locale in your navigation, i18n, or app configuration layer. Pass that value through contentful.defaultQuery, a managed source's entryQuery, or manual Contentful CDA requests, and pass the same value to SDK locale when Experience API responses and event context should use the same language. See Locale handling in the Optimization SDK Suite for the full locale model.

For provider-owned SDK runtimes, changing the locale prop calls sdk.setLocale() after initialization while the rest of the SDK config remains initialization-scoped. Locale updates do not fetch content or refresh profile state; trigger your app's normal screen(), identify(), or CDA fetch flow when localized data needs to change.

For every prop, callback payload, and exported type, use the generated React Native SDK reference.

Core workflows

Automatic navigation integrations use trackCurrentScreen() for current-screen deduplication. Direct manual screen() calls remain non-deduping event emits and return accepted/data event results.

Provider lifecycle

OptimizationRoot owns async React Native SDK initialization. It renders no children while platform state setup is pending, runs any onStatesReady callback, and only then renders provider children. This matches the React Web provider contract, but React Native uses async effect scheduling because storage and platform setup cannot be completed before paint.

When persistence consent permits durable profile continuity, SDK state from an Experience response is published only after the corresponding AsyncStorage write for that same response snapshot has settled or failed gracefully. AsyncStorage hydrates continuity during initialization and mirrors changes for the next launch; after startup, sdk.states, entry rendering, and tracking metadata read from in-memory SDK state. Application code can wait for sdk.states.profile, sdk.states.selectedOptimizations, or rendered SDK-derived UI instead of adding storage-timing delays before a relaunch-sensitive action.

Consent and persistence

Consent policy remains application-owned. For default-on application policies that do not render an end-user consent prompt, set defaults: { consent: true } on OptimizationRoot:

<OptimizationRoot clientId="your-client-id" defaults={{ consent: true }}>
  <YourApp />
</OptimizationRoot>

When application policy depends on user choice, leave defaults.consent unset and call consent(true | false) from the application-owned control. Boolean consent calls control both event emission and durable profile-continuity persistence by default. Use consent({ events: true, persistence: false }) when events are allowed but continuity should stay session-only.

Do not use a component effect to grant default consent for an app that has no consent prompt. Seeding defaults.consent during SDK initialization applies the persistence policy before child effects, screen tracking, or manual identify() calls can run.

defaults (and every other OptimizationRoot config prop except locale) is captured on first render. Reassigning defaults.consent after mount has no effect; call consent(true | false) from the SDK instance for runtime consent changes, or change the provider's React key to force full re-initialization.

AsyncStorage persists consent and, when persistence consent permits it, profile-continuity state across app launches. It is not consulted for every live state read after initialization. For cross-SDK consent guidance, see Consent management in the Optimization SDK Suite.

Render optimized entries

OptimizedEntry resolves optimized Contentful entries and passes non-optimized entries through unchanged. Prefer SDK-managed fetching when the SDK is configured with your app-owned contentful.js client. Use entryId with optional entryQuery, or pass a content-type/slug descriptor through managedEntry:

import { OptimizedEntry } from '@contentful/optimization-react-native'

function HeroEntry() {
  return (
    <OptimizedEntry
      managedEntry={{ contentType: 'page', slug: 'home', entryQuery: { locale: 'en-US' } }}
    >
      {(resolvedEntry) => <Hero data={resolvedEntry.fields} />}
    </OptimizedEntry>
  )
}

When an empty variant is selected, OptimizedEntry keeps its tracking View and resolution callbacks active but does not render or invoke its children.

Configure the SDK with contentful: { client } on OptimizationRoot, OptimizationProvider, or ContentfulOptimization.initialize(...). SDK-managed fetching merges contentful.defaultQuery, source entryQuery, the SDK locale fallback, and include: 10. slugField defaults to slug and can select a different field. Successful metadata, callbacks, and interaction tracking use the fetched entry's sys.id, not the slug. Manual baseline entries remain supported and unchanged:

<OptimizedEntry baselineEntry={entry}>
  {(resolvedEntry) => <Hero data={resolvedEntry.fields} />}
</OptimizedEntry>

Use prefetchManagedEntries on OptimizationRoot or OptimizationProvider to warm the SDK-managed entry cache after the React Native SDK is ready:

<OptimizationRoot
  clientId="your-client-id"
  contentful={{ client }}
  prefetchManagedEntries={[
    '4ib0hsHWoSOnCVdDkizE8d',
    { contentType: 'page', slug: 'home', entryQuery: { locale: 'en-US' } },
  ]}
>
  <YourApp />
</OptimizationRoot>

React Native has no SSR handoff path for provider/root cache warming. Use prefetchManagedEntries only for post-ready cache warmup.

Use one CDA locale for entries fetched through a managed source, passed to OptimizedEntry, or resolved with useEntryResolver() or useOptimizedEntry(). For localized apps, derive the application locale from your navigation, i18n, or app configuration layer and pass it to entryQuery, contentful.defaultQuery, or manual Contentful CDA requests. Do not pass all-locale CDA responses from withAllLocales or locale=*; these APIs expect direct single-locale field values.

Every entry the SDK resolves must include a top-level metadata object. Entries from contentfulClient.getEntry(...) carry it by default. Hand-built payloads (fixtures, cache snapshots, GraphQL responses) that omit metadata are silently returned as baseline with no error, which is indistinguishable from an entry that has no experience configured. See Entry optimization and variant resolution for the entry contract and Locale handling in the Optimization SDK Suite for the broader locale model.

Use useOptimizedEntry() when a component needs the same entryId, managedEntry, or baselineEntry source model without the OptimizedEntry wrapper:

import { useOptimizedEntry } from '@contentful/optimization-react-native'

function HeroData() {
  const { entry, isLoading, error, resolvedData } = useOptimizedEntry({
    managedEntry: { contentType: 'page', slug: 'home' },
  })

  if (isLoading || error || !entry || resolvedData.isEmptyVariant) return null

  return <Hero data={entry.fields} />
}

Use onEntryResolved, the render prop metadata, or the hook's metadata and isResolved fields when application code needs the baseline ID, resolved entry ID, or optimization context after tracking is ready.

To model baseline and variant entries with different content types, see Entry optimization and variant resolution.

Use useEntryResolver() when a component needs manual entry resolution without the OptimizedEntry wrapper:

import { useEntryResolver } from '@contentful/optimization-react-native'

function HeroData({ entry }) {
  const { resolveEntryData } = useEntryResolver()
  const resolvedData = resolveEntryData(entry)

  return resolvedData.isEmptyVariant ? null : <Hero data={resolvedData.entry.fields} />
}

resolveEntry() remains available when only the selected entry is needed, but its entry-only return value cannot distinguish an empty variant and should not make the final rendering decision.

Track entry interactions

Entry tracking records views and taps for Contentful entries, not arbitrary UI components. Global defaults live on OptimizationRoot and observe both views and taps by default. Individual OptimizedEntry components can override them:

<OptimizationRoot clientId="your-client-id" trackEntryInteraction={{ taps: false }}>
  <OptimizedEntry baselineEntry={entry} trackTaps={true}>
    {(resolvedEntry) => <Card entry={resolvedEntry} />}
  </OptimizedEntry>
</OptimizationRoot>

Wrap scrollable screens with OptimizationScrollProvider so view tracking uses actual scroll position instead of only screen dimensions.

Track screens

Use OptimizationNavigationContainer to emit screen events from React Navigation changes:

<OptimizationNavigationContainer>
  {(navigationProps) => (
    <NavigationContainer {...navigationProps}>{/* navigators */}</NavigationContainer>
  )}
</OptimizationNavigationContainer>

Use useScreenTracking() for screen-level control and useScreenTrackingCallback() when names are derived from navigation state or other dynamic data.

Provider-managed state subscriptions

Use onStatesReady when application code needs SDK state subscriptions that line up with provider initialization. The provider calls it after async SDK state setup completes and before child screen, navigation, or entry effects can emit events.

<OptimizationRoot
  clientId="your-client-id"
  onStatesReady={(states) => {
    const subscriptions = [
      states.eventStream.subscribe((event) => {
        if (event) devToolsPanel.logEvent(event)
      }),
      states.blockedEventStream.subscribe((blocked) => {
        if (blocked) devToolsPanel.logBlockedEvent(blocked)
      }),
    ]

    return () => {
      subscriptions.forEach((subscription) => subscription.unsubscribe())
    }
  }}
>
  <YourApp />
</OptimizationRoot>

The callback receives only sdk.states. Use regular hooks and React effects for component-local UI state under the provider.

Use OptimizationProvider directly with a pre-built sdk only when an application or framework adapter owns initialization. Without onStatesReady, children render immediately because the SDK is already available. When onStatesReady is provided, the provider waits until those subscribers are attached before children mount and runs the returned cleanup on unmount. In both cases, it does not call destroy() on the injected SDK.

Live updates and preview

liveUpdates controls whether OptimizedEntry continuously reacts to SDK state changes. The preview panel always forces live updates on while it is open.

<OptimizationRoot clientId="your-client-id" liveUpdates={true}>
  <OptimizedEntry baselineEntry={entry} liveUpdates={false}>
    {(resolvedEntry) => <Card entry={resolvedEntry} />}
  </OptimizedEntry>
</OptimizationRoot>

Enable the preview panel only in authoring or development flows and provide a Contentful client:

import { PreviewPanelOverlay } from '@contentful/optimization-react-native/preview'
;<OptimizationRoot clientId="your-client-id">
  <YourApp />
  {__DEV__ && <PreviewPanelOverlay contentfulClient={contentfulClient} />}
</OptimizationRoot>

Preview UI components and preview-specific types are exported from the preview subpath:

import { PreviewPanelOverlay } from '@contentful/optimization-react-native/preview'

Offline support

When NetInfo is installed, the SDK can queue events while the device is offline and flush them after connectivity returns. When the app moves to the background or inactive state, the SDK also flushes queued events and drains pending AsyncStorage persistence. Tune queue bounds and retry behavior with queuePolicy when the defaults are not appropriate for your app.

Runtime notes

  • ContentfulOptimization.initialize(...) is asynchronous. Prefer OptimizationRoot when React needs to own initialization.
  • View and tap tracking default to enabled.
  • Live updates default to disabled so entries lock to the first resolved value unless enabled globally, per component, or by the preview panel.
  • React Native compatibility polyfills are imported automatically for Iterator Helpers, crypto.randomUUID(), and crypto.getRandomValues(); applications do not need additional setup beyond installing this SDK and its documented dependencies.
  • Call destroy() before reinitializing the SDK in tests or hot-reload workflows.

Related