Skip to main content
The examples below cover the most common integration patterns you’ll encounter when adding Tappd to a React Native app. Each section is self-contained and copy-pasteable. Expand whichever scenario matches your use case and adapt the code to fit your app structure.
Track the full purchase funnel from product view through to order completion. Use Tappd’s standard e-commerce event names so your data maps cleanly to built-in dashboard reports.
import TappdSDK from '@tappd/mobile-sdk';

const tappd = new TappdSDK({ appId: 'YOUR_APP_ID' });

// 1. User views a product
await tappd.track('ecommerce.product_viewed', {
  productId: 'prod_123',
  productName: 'iPhone 15',
  category: 'Electronics',
  price: 999.00,
  currency: 'USD'
});

// 2. User adds item to cart
await tappd.track('ecommerce.add_to_cart', {
  productId: 'prod_123',
  productName: 'iPhone 15',
  quantity: 1,
  price: 999.00,
  currency: 'USD'
});

// 3. User begins checkout
await tappd.track('ecommerce.checkout_started', {
  cartValue: 999.00,
  currency: 'USD'
});

// 4. User completes purchase
await tappd.track('ecommerce.purchase', {
  orderId: 'ord_456',
  total: 999.00,
  currency: 'USD',
  items: [{
    productId: 'prod_123',
    name: 'iPhone 15',
    price: 999.00,
    quantity: 1
  }]
});
Track every step of the authentication flow and link anonymous pre-signup activity to the newly created account.Sign up flow:
async function handleSignUp(email, name) {
  // Track signup attempt
  await tappd.track('user.signup.started', {
    method: 'email'
  });

  try {
    // Create user account in your backend
    const user = await createAccount(email, name);

    // Identify the user — merges any pre-signup anonymous events
    await tappd.identify({
      external_id: user.id,
      email: email,
      name: name,
      signupDate: new Date().toISOString()
    });

    // Track success — no identifier needed, SDK associates automatically
    await tappd.track('user.signup.completed', {
      method: 'email'
    });
  } catch (error) {
    await tappd.track('user.signup.failed', {
      method: 'email',
      error: error.message
    });
  }
}
Login flow:
async function handleLogin(email) {
  await tappd.track('user.login.started', { method: 'email' });

  try {
    const user = await authenticate(email);

    await tappd.identify({
      external_id: user.id,
      email: user.email,
      name: user.name,
      lastLogin: new Date().toISOString()
    });

    await tappd.track('user.login.completed', { method: 'email' });
  } catch (error) {
    await tappd.track('user.login.failed', {
      method: 'email',
      error: error.message
    });
  }
}
Logout — reset the SDK to start a new anonymous session:
function handleLogout() {
  tappd.track('user.logout');

  // Clears user data and begins a fresh anonymous session
  tappd.reset();
}
With React Navigation (useFocusEffect):Use useFocusEffect so the screen view fires every time a screen comes into focus, not just on initial mount.
import React from 'react';
import { View, Text } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
import { useTappd } from '../contexts/TappdContext';

function ProductScreen({ route }) {
  const tappd = useTappd();
  const { productId } = route.params;

  useFocusEffect(
    React.useCallback(() => {
      tappd.trackScreen('ProductScreen', {
        productId: productId,
        category: 'ecommerce'
      });
    }, [productId])
  );

  return (
    <View>
      <Text>Product Screen</Text>
    </View>
  );
}
Manual tracking with useEffect:Use this approach for screens outside React Navigation’s stack.
import { useEffect } from 'react';
import { useTappd } from '../contexts/TappdContext';

function MyScreen() {
  const tappd = useTappd();

  useEffect(() => {
    tappd.trackScreen('MyScreen', {
      category: 'main',
      section: 'dashboard'
    });
  }, []);

  return (
    // Screen content
  );
}
This example covers the full push notification lifecycle: requesting permissions, getting the FCM token, registering it with Tappd, handling token refreshes, and tracking notification opens.
import React, { useEffect } from 'react';
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';
import TappdSDK from '@tappd/mobile-sdk';

const tappd = new TappdSDK({ appId: 'YOUR_APP_ID' });

function App() {
  useEffect(() => {
    setupPushNotifications();
  }, []);

  async function setupPushNotifications() {
    try {
      // Request permission (iOS only)
      if (Platform.OS === 'ios') {
        const authStatus = await messaging().requestPermission();
        const enabled =
          authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
          authStatus === messaging.AuthorizationStatus.PROVISIONAL;

        if (!enabled) return;
      }

      // Get the FCM token
      const token = await messaging().getToken();

      if (token) {
        // User must be identified before registering a push token
        await tappd.identify({
          external_id: 'user_123',
          email: 'john@example.com'
        });

        await tappd.registerPushToken(
          token,
          Platform.OS === 'ios' ? 'ios' : 'android'
        );
      }

      // Re-register whenever the token refreshes
      messaging().onTokenRefresh(async (newToken) => {
        await tappd.registerPushToken(
          newToken,
          Platform.OS === 'ios' ? 'ios' : 'android'
        );
      });

      // Track foreground message receipts
      messaging().onMessage(async (remoteMessage) => {
        await tappd.track('push_notification.received', {
          notificationId: remoteMessage.messageId,
          title: remoteMessage.notification?.title
        });
      });

      // Track notification-driven app opens (warm start)
      messaging().onNotificationOpenedApp(async (remoteMessage) => {
        await tappd.trackPushOpen(remoteMessage);
      });

      // Track notification-driven app opens (cold start)
      const initialMessage = await messaging().getInitialNotification();
      if (initialMessage) {
        await tappd.trackPushOpen(initialMessage);
      }
    } catch (error) {
      console.error('Push setup failed:', error);
    }
  }

  return (
    // Your app
  );
}
Track plan changes and update the customer profile in one flow.Subscription upgrade:
async function handleUpgrade(newPlan, userId) {
  const currentPlan = getUserPlan();

  await tappd.track('subscription.upgrade', {
    fromPlan: currentPlan,
    toPlan: newPlan,
    upgradeDate: new Date().toISOString()
  });

  // external_id must match the one used in identify()
  await tappd.setUserAttributes({
    external_id: userId,
    plan: newPlan,
    upgradedAt: new Date().toISOString()
  });
}
Subscription cancellation:
async function handleCancellation(reason, userId) {
  await tappd.track('subscription.cancelled', {
    reason: reason,
    cancelledAt: new Date().toISOString()
  });

  await tappd.setUserAttributes({
    external_id: userId,
    planStatus: 'cancelled',
    cancellationDate: new Date().toISOString()
  });
}
Track when the app moves between states and clean up the SDK when it goes to the background.
import { AppState } from 'react-native';
import { useEffect } from 'react';
import { useTappd } from '../contexts/TappdContext';

function App() {
  const tappd = useTappd();

  useEffect(() => {
    const subscription = AppState.addEventListener('change', (nextAppState) => {
      if (nextAppState === 'active') {
        tappd.track('app.foreground');
      } else if (nextAppState === 'background') {
        tappd.track('app.background');
        tappd.cleanup(); // Flush pending events before backgrounding
      }
    });

    return () => {
      subscription.remove();
    };
  }, []);

  return (
    // Your app
  );
}
Wrap a TouchableOpacity in a reusable component that tracks every press automatically.
import { TouchableOpacity, Text } from 'react-native';
import { useTappd } from '../contexts/TappdContext';

function MyButton({ buttonId, title, onPress }) {
  const tappd = useTappd();

  const handlePress = async () => {
    await tappd.track('button_click', {
      buttonId: buttonId,
      buttonText: title,
      location: 'home_screen'
    });

    if (onPress) onPress();
  };

  return (
    <TouchableOpacity onPress={handlePress}>
      <Text>{title}</Text>
    </TouchableOpacity>
  );
}

// Usage:
// <MyButton
//   buttonId="signup_button"
//   title="Sign Up"
//   onPress={() => navigation.navigate('SignUp')}
// />
Track individual field interactions and form submissions to understand where users drop off in your sign-up funnel.
import { useState } from 'react';
import { TextInput, Button } from 'react-native';
import { useTappd } from '../contexts/TappdContext';

function SignUpForm() {
  const tappd = useTappd();
  const [formData, setFormData] = useState({});

  const handleFieldFocus = async (fieldName) => {
    await tappd.track('form.field_focused', {
      formId: 'signup_form',
      fieldName: fieldName
    });
  };

  const handleSubmit = async () => {
    await tappd.track('form.submitted', {
      formId: 'signup_form',
      formName: 'Sign Up',
      fieldsCompleted: Object.keys(formData).length
    });

    await submitForm(formData);
  };

  return (
    <>
      <TextInput
        placeholder="Email"
        onFocus={() => handleFieldFocus('email')}
        onChangeText={(text) => setFormData({ ...formData, email: text })}
      />
      <Button title="Submit" onPress={handleSubmit} />
    </>
  );
}
Capture unhandled JavaScript errors and fatal crashes with React Native’s global error handler, then forward them to Tappd for analysis.
import { ErrorUtils } from 'react-native';
import TappdSDK from '@tappd/mobile-sdk';

const tappd = new TappdSDK({ appId: 'YOUR_APP_ID' });

ErrorUtils.setGlobalHandler(async (error, isFatal) => {
  try {
    await tappd.track('error', {
      message: error.message,
      stack: error.stack,
      isFatal: isFatal,
      timestamp: new Date().toISOString()
    });
  } catch (trackingError) {
    console.error('Failed to track error:', trackingError);
  }

  // Preserve the default error behaviour
  if (ErrorUtils.getGlobalHandler) {
    ErrorUtils.getGlobalHandler()(error, isFatal);
  }
});
Track every deep link open and distinguish between cold starts (app was not running) and warm starts (app was already in memory).
import { Linking } from 'react-native';
import { useEffect } from 'react';
import { useTappd } from '../contexts/TappdContext';

function App() {
  const tappd = useTappd();

  useEffect(() => {
    // Handle the link that launched the app from a closed state
    Linking.getInitialURL().then((url) => {
      if (url) {
        tappd.track('deep_link.opened', {
          url: url,
          source: 'cold_start'
        });
      }
    });

    // Handle links when the app is already running
    const subscription = Linking.addEventListener('url', (event) => {
      tappd.track('deep_link.opened', {
        url: event.url,
        source: 'warm_start'
      });
    });

    return () => {
      subscription.remove();
    };
  }, []);

  return (
    // Your app
  );
}