Skill 09 · Contentful Personalization
Subchapter 9.16
references/optimization-react-native.mdMarkdown6 KBView on GitHub
Use native screen, tap, and viewport terminology. Do not transplant Web page, click, hover, cookie, or DOM behavior into this runtime.
pnpm add @contentful/optimization-react-native \
@react-native-async-storage/async-storage \
contentfulAsyncStorage is required. NetInfo is optional and enables offline detection. Clipboard and safe-area context are optional until the preview panel is used. Rebuild the native application after adding native peers; Expo Go cannot host the preview panel’s native modules, so Expo projects need a custom development build.
import { OptimizationRoot } from '@contentful/optimization-react-native';
import { createClient } from 'contentful';
const contentfulClient = createClient({
space: process.env.EXPO_PUBLIC_CONTENTFUL_SPACE_ID!,
accessToken: process.env.EXPO_PUBLIC_CONTENTFUL_DELIVERY_TOKEN!,
});
export function AppRoot() {
return (
<OptimizationRoot
clientId={process.env.EXPO_PUBLIC_CONTENTFUL_OPTIMIZATION_CLIENT_ID!}
environment="main"
locale="en-US"
defaults={{ consent: true }}
contentful={{ client: contentfulClient }}
>
<App />
</OptimizationRoot>
);
}OptimizationRoot initializes asynchronously and withholds its children while an owned instance is
loading. The SDK allows one active instance. Use ContentfulOptimization.create(config) plus
<OptimizationProvider sdk={sdk}> only when application or test code must own the instance; the
owner must then call destroy().
Replace the quick-start consent default with application policy. The React Native default
pre-consent allow-list admits identify and screen; entry views, taps, page events, and custom
events remain blocked until allowed.
Managed path:
import { OptimizedEntry } from '@contentful/optimization-react-native';
<OptimizedEntry
entryId="hero-entry-id"
loadingFallback={<LoadingHero />}
errorFallback={(error) => <HeroError error={error} />}
>
{(entry) => <Hero entry={entry as HeroEntry} />}
</OptimizedEntry>;Manual path:
<OptimizedEntry baselineEntry={entry}>
{(resolvedEntry, metadata) => (
<Hero entry={resolvedEntry as HeroEntry} experienceId={metadata.selectedOptimization?.experienceId} />
)}
</OptimizedEntry>Pass baselineEntry or entryId, never both. Managed loading and fetch errors are distinct from a
valid baseline resolution. Static children are supported for tracking-only wrappers but cannot
receive variant data.
For a simple screen:
import { useScreenTracking } from '@contentful/optimization-react-native';
function ProductScreen() {
useScreenTracking({ name: 'Product' });
return <Product />;
}For React Navigation, use OptimizationNavigationContainer and pass its render-prop ref,
onReady, and onStateChange to the navigation container. Do not also mount per-screen automatic
tracking for the same route. Automatic screen tracking deduplicates the current route; the callback
and returned imperative trackScreen() emit directly.
OptimizedEntry enables viewport views and taps by default. It renders a View with layout and
touch handlers around resolved content. Configure globally:
<OptimizationRoot {...config} trackEntryInteraction={{ views: true, taps: true }}>
<App />
</OptimizationRoot>Use trackViews, trackTaps, and onTap per entry. Wrap scrollable personalized content in
OptimizationScrollProvider so visibility uses the real scroll viewport. Default view thresholds
are 80% visible for two seconds, followed by five-second duration updates. Tap recognition requires
less than ten points of movement, so scrolling is not counted as a tap.
Access the initialized SDK through useOptimization():
const sdk = useOptimization();
await sdk.identify({ userId: user.id, traits: { plan: user.plan } });
sdk.consent({ events: true, persistence: true });
sdk.reset();React Native stores consent state in AsyncStorage. Profile, changes, selections, and the anonymous
ID are stored only when persistence consent is true. Event queues are in memory and do not survive a
process restart. reset() clears profile continuity and screen deduplication, but application-owned
authentication and consent records remain application responsibilities.
React Native uses AsyncStorage keys, not the browser ctfl-opt-aid cookie. There is no built-in
cross-platform cookie handoff.
@react-native-community/netinfo to gate event flushing on connectivity. Without it,
offline detection is disabled. Replay remains in-memory only.PreviewPanelOverlay from @contentful/optimization-react-native/preview, supply the
application Contentful client, and render it inside OptimizationRoot or inside both
OptimizationProvider and LiveUpdatesProvider.10.0.2.2; the SDK
does not do this automatically.