Skip to main content

Core Concepts

Understand how the Rocapine Onboarding Studio SDK works under the hood.

Architecture Overview

Onboarding Studio follows a headless-first architecture, separating data management from UI rendering. This gives you maximum flexibility to use pre-built components or build your own UI.

Package Structure

The SDK is split into two packages:

  1. @rocapine/react-native-onboarding (Core/Headless)

    • Data fetching and caching
    • State management and hooks
    • Type definitions
    • No UI components
  2. @rocapine/react-native-onboarding-ui (Optional UI)

    • Pre-built screen components
    • Theme system
    • Styled components for all page types

Architecture Layers

┌─────────────────────────────────────┐
│ Your React Native App │
│ (Navigation, Routing, Business) │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│ UI Layer (Your Choice) │
│ • Pre-built: OnboardingPage │
│ • Custom: Your own components │
│ • Mixed: Both approaches │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│ Core SDK (Headless) │
│ • OnboardingProvider (Data) │
│ • useOnboardingStep (Hooks) │
│ • Caching & State Management │
│ • Type-safe data structures │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│ CMS Backend │
│ (Supabase - Onboarding Studio) │
└─────────────────────────────────────┘

Headless-First Benefits

  • Flexibility: Build completely custom UIs that match your brand
  • Bundle Size: Only install UI components you actually use
  • Incremental Adoption: Start with pre-built components, migrate to custom UI later
  • Framework Agnostic: Core SDK works with any React Native navigation solution

How It Works

1. Initialization

When you wrap your app with OnboardingProvider and ThemeProvider:

<OnboardingProvider
client={client}
customAudienceParams={{ onboardingId: "abc-123" }}
>
<ThemeProvider>
<YourApp />
</ThemeProvider>
</OnboardingProvider>

The SDK:

  1. Initializes React Query for data fetching (OnboardingProvider)
  2. Sets up AsyncStorage caching (OnboardingProvider)
  3. Prepares theme context (ThemeProvider)
  4. Establishes progress tracking (OnboardingProvider)

2. Data Fetching

When you call useOnboardingQuestions({ stepNumber: 1 }):

const {
step,
isLastStep,
stepsLength,
onboardingMetadata,
steps
} = useOnboardingQuestions({ stepNumber: 1 });

The SDK:

  1. Checks AsyncStorage cache first (offline support)
  2. Fetches from CMS if cache is stale or missing
  3. Uses React Query's useSuspenseQuery for automatic loading states
  4. Validates data with Zod schemas
  5. Updates progress context

3. Rendering

You have two options for rendering:

Option A: Use Pre-built Components (from @rocapine/react-native-onboarding-ui)

import { OnboardingPage } from "@rocapine/react-native-onboarding-ui";

<OnboardingPage step={step} onContinue={handleContinue} />

The pre-built component:

  1. Inspects the step.type field
  2. Routes to the appropriate renderer (QuestionRenderer, MediaContentRenderer, etc.)
  3. Passes step data and callbacks
  4. Handles fallback for unimplemented types (sandbox mode)

Option B: Build Your Own UI (Headless approach)

import { useOnboardingStep } from "@rocapine/react-native-onboarding";

const { step } = useOnboardingStep({ stepNumber: 1 });

// Build your own UI based on step.type and step.payload
switch (step.type) {
case "Question":
return <YourCustomQuestionUI step={step} />;
case "MediaContent":
return <YourCustomMediaUI step={step} />;
// ... handle other types
}

This gives you complete control over the UI while still benefiting from the SDK's data fetching, caching, and type safety.

Caching Strategy

AsyncStorage Cache

The SDK uses AsyncStorage to cache onboarding steps for offline access.

Cache Behavior:

  • Steps are cached after successful fetch
  • Cache is checked before network requests
  • Stale data triggers background refresh
  • Cache persists across app restarts

Cache Invalidation: You can manually invalidate the cache:

import { useQueryClient } from "@tanstack/react-query";

const queryClient = useQueryClient();
queryClient.invalidateQueries({ queryKey: ["onboardingSteps"] });

Progress Tracking

The SDK automatically tracks progress through your onboarding flow:

Progress Context

const { activeStep, totalSteps, progress } = useContext(OnboardingProgressContext);
  • activeStep: Current step number (updated by useOnboardingQuestions)
  • totalSteps: Total number of steps in the flow
  • progress: Percentage (0-1) of completion

ProgressBar Component

Add <ProgressBar /> once in your app layout, typically inside both providers:

<OnboardingProvider {...props}>
<ThemeProvider>
<ProgressBar /> {/* Global progress indicator */}
<YourApp />
</ThemeProvider>
</OnboardingProvider>

The ProgressBar:

  • Reads from progress context automatically
  • Shows/hides based on step.displayProgressHeader
  • Animates smoothly with Reanimated
  • Positions itself at the top of safe area

Step Types

Each step returned by the CMS has this structure:

{
id: string; // Unique identifier
type: string; // "Question" | "MediaContent" | "Carousel" | ...
name: string; // Display name (for debugging)
displayProgressHeader: boolean; // Show/hide progress bar
payload: object; // Type-specific data
customPayload: object | null; // Your custom fields
continueButtonLabel?: string; // Optional CTA text
figmaUrl?: string | null; // Design reference
}

Type Discrimination

The SDK uses the type field for routing:

switch (step.type) {
case "Question":
return <QuestionRenderer step={step} onContinue={onContinue} />;
case "MediaContent":
return <MediaContentRenderer step={step} onContinue={onContinue} />;
// ... more types
}

Runtime Validation

All step data is validated at runtime using Zod schemas:

const QuestionStepSchema = z.object({
id: z.string(),
type: z.literal("Question"),
payload: z.object({
title: z.string(),
subtitle: z.string().nullish(),
answers: z.array(/* ... */),
// ...
}),
// ...
});

If data doesn't match the schema, you'll get a clear error message.

Sandbox Mode

Enable sandbox mode during development:

const client = new OnboardingStudioClient("your-project-id", {
isSandbox: true,
});

Sandbox Features:

  • Shows dev messages for unimplemented screen types
  • More verbose error messages
  • Allows testing without production CMS data
  • Auto-continues on unimplemented screens

Production Mode:

  • Silently auto-continues on unimplemented screens
  • Minimal error messages
  • Optimized for end-user experience

Locale Support

Fetch onboarding content in different languages:

<OnboardingProvider locale="fr" />

The locale is passed to the CMS API, which returns localized content based on your Onboarding Studio configuration.

Custom Headers

The CMS API returns custom headers with metadata:

  • ONBS-Onboarding-Id: The active onboarding ID
  • ONBS-Audience-Id: The targeted audience
  • ONBS-Onboarding-Name: Display name of the onboarding

These headers are available in the client but typically not needed in your app logic.

Suspense Boundaries

The SDK uses React Query's useSuspenseQuery, which requires Suspense boundaries:

import { Suspense } from "react";

<Suspense fallback={<LoadingScreen />}>
<OnboardingScreen />
</Suspense>
tip

Expo Router handles Suspense automatically, so you usually don't need to add boundaries manually.

Error Handling

The SDK provides multiple layers of error handling:

  1. Network Errors: Automatic retry with exponential backoff
  2. Validation Errors: Clear messages about schema mismatches
  3. Missing Dependencies: Explicit instructions for required packages
  4. Sandbox Mode: Development-friendly error messages

Theme System

The SDK provides a theme system via ThemeProvider from the UI SDK with:

  • Color Tokens: Semantic color names (primary, neutral, text, etc.)
  • Typography Tokens: Font families, sizes, weights, line heights
  • Semantic Text Styles: Pre-configured styles (heading1, body, button, etc.)
  • Mode Support: Light and dark mode with separate token sets

Learn more in Customization → Theming.

Performance Considerations

Optimizations

  • Lazy Loading: Renderers are lazy-loaded per screen type
  • Native Driver: Animations use native driver when possible
  • Memoization: Components are memoized where appropriate
  • Query Deduplication: React Query prevents duplicate requests

Best Practices

  • Use pagination for long onboarding flows
  • Preload media assets when possible
  • Keep payload sizes reasonable
  • Cache static assets locally

Next Steps