> ## 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.

# React Native Integration Patterns

> Two ways to handle Encore offer results in React Native — async-result and handler-based — with a decision tree for picking the right one.

## 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](/concepts/integration-patterns).

***

## Async-result pattern

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

### Basic example

```tsx theme={null}
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

```tsx theme={null}
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

```tsx theme={null}
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](../reference/placement-result).

***

## 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

```tsx theme={null}
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;
}
```

<Warning>
  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()`](../reference/complete-purchase-request).
</Warning>

### Then `show()` from anywhere

```tsx theme={null}
<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](/concepts/integration-patterns#decision-tree) for the full rationale.

***

## Re-registration semantics

```tsx theme={null}
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

* [Concepts — Integration Patterns](/concepts/integration-patterns) — cross-platform decision tree
* [Quickstart — Present Offers](/publishers/react-native/quickstart/present-offers) — handler-pattern walkthrough
* [SDK Reference — `placement()`](../reference/placement)
* [SDK Reference — `PlacementResult`](../reference/placement-result)
* [SDK Reference — `completePurchaseRequest()`](../reference/complete-purchase-request)
