Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encorekit.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Encore.placement(id).show() returns a Promise<PlacementResult>. You can either await it and branch on the returned value (async-result) or register global onPurchaseRequest / onPassthrough handlers (handler-based). Both work; pick whichever fits the call site. For the cross-platform decision tree, see Integration Patterns.

Async-result pattern

Use this when the component that triggers show() already has access to your purchase + decline logic.

Basic example

import Encore from '@tryencorekit/react-native';

async function onCancelTapped() {
  const result = await Encore.placement('cancel_flow').show();

  switch (result.status) {
    case 'granted':
    case 'completed':
      // User accepted
      navigation.navigate('Home');
      break;
    case 'not_granted':
    case 'dismissed':
    case 'no_offers':
      // User declined or no offers — proceed with original flow
      await proceedWithCancellation(result.reason);
      break;
  }

  // Code that runs in both branches lives here, written once
  analytics.track('cancel_flow_resolved');
}

From a component

function CancelScreen() {
  const handleCancel = async () => {
    const result = await Encore.placement('cancel_flow').show();
    if (result.status === 'granted' || result.status === 'completed') {
      navigation.navigate('Home');
    } else {
      await proceedWithCancellation(result.reason);
    }
  };

  return <Button title="Cancel Subscription" onPress={handleCancel} />;
}

Result type

interface PlacementResult {
  status: 'granted' | 'not_granted' | 'completed' | 'dismissed' | 'no_offers';
  reason?: string;
  entitlement?: string;
  offerId?: string;
  campaignId?: string;
}
iOS surfaces granted / not_granted; Android surfaces completed / dismissed / no_offers. Treat both grant cases (granted, completed) as success and the rest as decline. See PlacementResult.

Handler pattern

Use this when show() is invoked from many sites that share post-purchase logic, or when the trigger is far from your billing code.

Register at app startup

import { useEffect } from 'react';
import Encore, { EncoreProvider } from '@tryencorekit/react-native';

export default function App() {
  return (
    <EncoreProvider apiKey="pk_live_...">
      <RootHandlers />
      <MainNavigator />
    </EncoreProvider>
  );
}

function RootHandlers() {
  useEffect(() => {
    const unsubPurchase = Encore.onPurchaseRequest(async ({ productId }) => {
      try {
        await billing.purchase(productId);
        await Encore.completePurchaseRequest(true);
      } catch {
        await Encore.completePurchaseRequest(false);
      }
    });

    const unsubPassthrough = Encore.onPassthrough(({ placementId }) => {
      AppRouter.handlePassthrough(placementId);
    });

    return () => {
      unsubPurchase();
      unsubPassthrough();
    };
  }, []);

  return null;
}
You must call Encore.completePurchaseRequest(success) in every code path of onPurchaseRequest. Failing to do so blocks the SDK from presenting future offers. See completePurchaseRequest().

Then show() from anywhere

<Button title="Cancel Subscription" onPress={() => Encore.placement('cancel_flow').show()} />

When to use which

  • Async-result — component already imports your billing client. Branch-specific code lives at the call site.
  • Handler — registered once at app root; many show() sites share the same purchase delegation.
  • Mixed — register handlers for cross-cutting telemetry; still await show() for site-specific UI navigation.
See the decision tree for the full rationale.

Re-registration semantics

const unsub1 = Encore.onPurchaseRequest(handlerV1);
const unsub2 = Encore.onPurchaseRequest(handlerV2); // replaces handlerV1
Internally, each setter calls currentSubscription?.remove() before adding the new listener — there is no double-fire. The returned unsubscribe function is useful for component lifecycles (call from a useEffect cleanup) but is not required to prevent double-fires when you replace via re-registration.

Platform-specific notes

  • Status values vary by platform — iOS surfaces granted / not_granted; Android surfaces completed / dismissed / no_offers. Code that handles both cases is portable.
  • Event-emitter under the hood — handlers are wired via NativeEventEmitter, but the JS API has replace semantics on the JS side as well. You don’t need to manually remove listeners between re-registrations.
  • completePurchaseRequest is required — unlike iOS / Android native SDKs where onPurchaseRequest simply throws or returns, RN’s bridge needs an explicit ack. Always call it.
  • Provider — wrap your root in <EncoreProvider apiKey="..."> so the bridge is configured before any show() call.

See also