> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tappd.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Placement-Based Mobile Banners with Targeting and A/B Tests

> Fetch and render placement-based banners in React Native with built-in support for audience targeting, scheduling, frequency capping, and A/B testing.

Tappd banners are placement-based content units you create in the dashboard and fetch in your React Native app using a placement identifier — typically a screen name or component ID. Unlike in-app messages, which are trigger-driven and shown once, banners are designed for persistent placements and support advanced delivery controls like audience targeting, scheduling windows, frequency capping, and multi-variant A/B testing.

## Quick Start

Identify your user, fetch banners for a placement, and render each result with your own component:

```javascript theme={null}
import TappdSDK from '@tappd/mobile-sdk';

const tappd = new TappdSDK({
  appId: 'YOUR_APP_ID',
  apiUrl: 'https://sdk.gotappd.com/api/v1/sdk',
});

// Identify the user first — required for targeting to work
await tappd.identify({
  external_id: 'user_123',
  email: 'john@example.com',
  name: 'John Doe',
});

// Fetch banners for the HomeScreen placement
const banners = await tappd.getBanners('HomeScreen');

banners.forEach(({ banner, variant }) => {
  // Render your banner component with the returned data
});
```

## Placement System

Banners attach to your UI via a placement identifier — a string you define in the dashboard and pass to `getBanners()`. Use whatever naming convention makes sense for your app:

```javascript theme={null}
// Screen-based placement — use screen names
const banners = await tappd.getBanners('HomeScreen');

// Component-based placement — use component identifiers
const banners = await tappd.getBanners('header-banner');
```

<Tip>
  Use screen names as placement identifiers whenever possible. Mapping banners
  to screens keeps your placements predictable and makes it easy to reload
  banners when a user navigates to a new screen.
</Tip>

## Fetching Banners

`getBanners()` automatically caches results for 5 minutes to reduce API calls. Use the optional second argument to control caching and rendering behaviour:

```javascript theme={null}
// Fetch with caching (default — 5-minute cache)
const banners = await tappd.getBanners('HomeScreen');

// Force a fresh fetch, bypassing the cache
const banners = await tappd.getBanners('HomeScreen', { forceRefresh: true });

// Auto-render eligible banners immediately after fetching
const banners = await tappd.getBanners('HomeScreen', { autoRender: true });
```

| Option         | Type      | Default | Description                                                                                                                                                             |
| -------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autoRender`   | `boolean` | `false` | When `true`, the SDK automatically renders eligible banners for the placement immediately after fetching, without requiring you to call `banners.display()` separately. |
| `forceRefresh` | `boolean` | `false` | Bypass the 5-minute result cache and request fresh data from the API.                                                                                                   |

## Banner Response Structure

Each item in the response array contains a `banner` object and the winning `variant` object selected for the current user:

```json theme={null}
[
  {
    "banner": {
      "_id": "banner_id_123",
      "name": "Summer Sale Banner",
      "placement": {
        "type": "css_selector",
        "cssSelector": "HomeScreen"
      }
    },
    "variant": {
      "_id": "variant_id_456",
      "name": "Variant A",
      "content": {
        "title": "Summer Sale",
        "message": "Get 50% off on all items!",
        "imageUrl": "https://example.com/banner.jpg",
        "link": "https://example.com/sale",
        "cta": {
          "text": "Shop Now",
          "link": "https://example.com/sale",
          "action": "link"
        }
      },
      "design": {
        "position": "top",
        "layout": "horizontal",
        "backgroundColor": "#ffffff",
        "textColor": "#000000",
        "ctaBackgroundColor": "#007bff",
        "ctaTextColor": "#ffffff",
        "fontFamily": "System",
        "fontSize": "16px",
        "padding": "16px",
        "margin": "0",
        "borderRadius": "4px"
      },
      "config": {
        "dismissible": true,
        "autoHide": false,
        "autoHideDelay": 5000,
        "animation": "fade",
        "closeButton": true
      }
    }
  }
]
```

## Rendering Banners in React Native

Build a reusable `Banner` component that reads from the `banner` and `variant` objects returned by the API:

```javascript theme={null}
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  Image,
  TouchableOpacity,
  StyleSheet,
  Animated,
  Linking,
} from 'react-native';
import TappdSDK from '@tappd/mobile-sdk';

const tappd = new TappdSDK({
  appId: 'YOUR_APP_ID',
  apiUrl: 'https://sdk.gotappd.com/api/v1/sdk',
});

function Banner({ banner, variant, onDismiss }) {
  const [fadeAnim] = useState(new Animated.Value(0));
  const [displayId, setDisplayId] = useState(null);

  useEffect(() => {
    // Track the display and store the displayId for click/dismiss tracking
    tappd.banners
      .display(banner._id, variant._id, banner.placement.cssSelector)
      .then((id) => setDisplayId(id));

    // Animate in
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true,
    }).start();

    // Auto-hide if configured
    if (variant.config.autoHide) {
      const timer = setTimeout(handleDismiss, variant.config.autoHideDelay);
      return () => clearTimeout(timer);
    }
  }, []);

  const handleDismiss = async () => {
    if (displayId) {
      await tappd.banners.dismiss(banner._id, displayId);
    }
    onDismiss();
  };

  const handleCTAClick = async () => {
    if (displayId) {
      await tappd.banners.click(banner._id, displayId);
    }

    const cta = variant.content.cta;
    if (cta.action === 'link' && cta.link) {
      Linking.openURL(cta.link);
    } else if (cta.action === 'close') {
      handleDismiss();
    }
  };

  const { design, content } = variant;

  return (
    <Animated.View
      style={[
        styles.banner,
        {
          opacity: fadeAnim,
          backgroundColor: design.backgroundColor,
          padding: parseInt(design.padding) || 16,
          borderRadius: parseInt(design.borderRadius) || 4,
        },
      ]}
    >
      <View style={styles.content}>
        {content.imageUrl && (
          <Image
            source={{ uri: content.imageUrl }}
            style={styles.image}
            resizeMode="contain"
          />
        )}

        <View style={styles.textContainer}>
          {content.title && (
            <Text style={[styles.title, { color: design.textColor }]}>
              {content.title}
            </Text>
          )}
          {content.message && (
            <Text style={[styles.message, { color: design.textColor }]}>
              {content.message}
            </Text>
          )}
        </View>

        {content.cta?.text && (
          <TouchableOpacity
            style={[
              styles.ctaButton,
              { backgroundColor: design.ctaBackgroundColor },
            ]}
            onPress={handleCTAClick}
          >
            <Text style={[styles.ctaText, { color: design.ctaTextColor }]}>
              {content.cta.text}
            </Text>
          </TouchableOpacity>
        )}

        {variant.config.dismissible && variant.config.closeButton && (
          <TouchableOpacity style={styles.closeButton} onPress={handleDismiss}>
            <Text style={styles.closeButtonText}>×</Text>
          </TouchableOpacity>
        )}
      </View>
    </Animated.View>
  );
}

const styles = StyleSheet.create({
  banner: { width: '100%', marginBottom: 8 },
  content: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
  image: { width: 60, height: 60, marginRight: 12 },
  textContainer: { flex: 1, marginRight: 12 },
  title: { fontWeight: 'bold', marginBottom: 4 },
  message: { fontSize: 14 },
  ctaButton: { paddingHorizontal: 16, paddingVertical: 8, marginLeft: 8 },
  ctaText: { fontWeight: '600', fontSize: 14 },
  closeButton: { padding: 8, marginLeft: 8 },
  closeButtonText: { fontSize: 24, color: '#666' },
});

export default Banner;
```

## Manual Event Tracking

Call the `banners` tracking methods explicitly when you need fine-grained control over when events are recorded:

<Steps>
  <Step title="Track display">
    Call `banners.display()` when the banner becomes visible. Store the returned
    `displayId` — you need it for click and dismiss tracking.

    ```javascript theme={null}
    const displayId = await tappd.banners.display(
      bannerId,   // Banner ID
      variantId,  // Variant ID
      selector    // Placement selector (e.g., 'HomeScreen')
    );
    ```
  </Step>

  <Step title="Track clicks">
    Call `banners.click()` when the user taps the CTA button.

    ```javascript theme={null}
    await tappd.banners.click(bannerId, displayId);
    ```
  </Step>

  <Step title="Track dismissals">
    Call `banners.dismiss()` when the user closes the banner.

    ```javascript theme={null}
    await tappd.banners.dismiss(bannerId, displayId);
    ```
  </Step>
</Steps>

## Banner Position Handling

Use the `position` value from `variant.design` to apply the correct layout style:

```javascript theme={null}
function Banner({ banner, variant, ...props }) {
  const position = variant.design.position || 'top';

  const positionStyles = {
    top: { position: 'absolute', top: 0, left: 0, right: 0, zIndex: 1000 },
    bottom: { position: 'absolute', bottom: 0, left: 0, right: 0, zIndex: 1000 },
    center: { alignItems: 'center', justifyContent: 'center' },
  };

  return (
    <View style={[styles.banner, positionStyles[position]]}>
      {/* Banner content */}
    </View>
  );
}
```

## Features

<CardGroup cols={2}>
  <Card title="Audience Targeting" icon="crosshairs">
    Show banners only to specific customer segments or users matching custom
    attribute rules. Banners with targeting enabled are only returned when the
    identified user meets the criteria.
  </Card>

  <Card title="Scheduling" icon="calendar">
    Set start and end dates, restrict banners to specific hours of the day or
    days of the week, and define a timezone for accurate schedule calculations.
  </Card>

  <Card title="Frequency Capping" icon="gauge">
    Limit how many times a banner is shown to an individual user. Configure a
    maximum display count and a reset period — session, hour, day, week, or
    month.
  </Card>

  <Card title="A/B Testing" icon="flask">
    Run experiments with multiple variants. Set traffic allocation percentages
    for each variant and let the SDK automatically assign users based on their
    customer ID.
  </Card>
</CardGroup>

### Analytics

Every banner interaction is tracked automatically when you call the `banners.*` methods:

| Metric      | Description                                     |
| ----------- | ----------------------------------------------- |
| Views       | Times the banner was displayed                  |
| Clicks      | Times the CTA was tapped                        |
| Dismissals  | Times the banner was closed                     |
| Conversions | Downstream conversions attributed to the banner |
| CTR         | Click-through rate (clicks ÷ views)             |

## API Reference

<ParamField body="getBanners(selector, options?)" type="Promise<Banner[]>">
  Fetch eligible banners for a placement.

  * `selector` *(string, required)* — Placement identifier (screen name, component name, etc.)
  * `options.autoRender` *(boolean, default `false`)* — Automatically render eligible banners after fetching, without a separate `banners.display()` call.
  * `options.forceRefresh` *(boolean, default `false`)* — Bypass the 5-minute cache and fetch fresh data.
</ParamField>

<ParamField body="banners.display(bannerId, variantId, selector)" type="Promise<string>">
  Track a banner display. Returns a `displayId` string you must store and pass to `banners.click()` and `banners.dismiss()`.
</ParamField>

<ParamField body="banners.click(bannerId, displayId)" type="Promise<void>">
  Track a banner CTA click. Pass the `displayId` returned by `banners.display()`.
</ParamField>

<ParamField body="banners.dismiss(bannerId, displayId)" type="Promise<void>">
  Track a banner dismissal. Pass the `displayId` returned by `banners.display()`.
</ParamField>

## Best Practices

<Tip>
  Keep these tips in mind for reliable banner delivery and accurate analytics:

  * **Identify users early** — Call `identify()` before fetching banners so targeting rules can be evaluated.
  * **Reload on navigation** — Fetch banners again whenever the user navigates to a new screen so placement-specific content stays fresh.
  * **Always track all three events** — Display, click, and dismiss events together give you accurate CTR and frequency capping data.
  * **Lean on caching** — The SDK's 5-minute cache reduces API overhead. Use `forceRefresh` only when you know the content has changed.
  * **Handle errors gracefully** — Wrap `getBanners()` calls in try-catch blocks so a failed fetch doesn't break your screen.
</Tip>

## Troubleshooting

<Accordion title="Banners not displaying">
  If `getBanners()` returns an empty array or banners don't appear in your UI:

  1. Make sure `identify()` was called before `getBanners()`.
  2. Confirm the placement identifier exactly matches what's configured in the dashboard.
  3. Check the browser console or device logs for network errors.
  4. Verify the banner is published and active in the Tappd dashboard.
  5. Review targeting conditions — the identified user must match any configured segment or rule.
  6. Check that the banner's schedule window is currently active.
  7. Confirm frequency capping hasn't been reached for this user.
</Accordion>

<Accordion title="Banners displaying incorrectly">
  If banners appear but look wrong:

  1. Verify you're reading style values from `variant.design` and applying them correctly to your component.
  2. Check that the container has explicit dimensions — React Native won't size a component with no content or constraints.
  3. Look for style conflicts between your app's global styles and the banner component.
  4. Ensure `positionStyles` are applied for absolute-positioned banners (top/bottom).
</Accordion>

<Accordion title="Tracking not working">
  If clicks and dismissals aren't appearing in Tappd analytics:

  1. Confirm `apiUrl` in your SDK config points to `https://sdk.gotappd.com/api/v1/sdk`.
  2. Make sure you're storing the `displayId` returned by `banners.display()` and passing it to `banners.click()` and `banners.dismiss()`.
  3. Check device logs for network errors on the tracking API calls.
  4. Verify your `appId` is correct.
</Accordion>
